Repository: johnsonjh/OpenVi Branch: master Commit: 7b40c9ceffb2 Files: 280 Total size: 2.9 MB Directory structure: gitextract_apaa2j_q/ ├── .gitattributes ├── .gitignore ├── BSDmakefile ├── ChangeLog ├── ChangeLog.license ├── GNUmakefile ├── LICENSE.md ├── LICENSES/ │ ├── BSD-2-Clause.txt │ ├── BSD-3-Clause.txt │ └── ISC.txt ├── README.md ├── README_pt_BR.md ├── REUSE.toml ├── cl/ │ ├── cl.h │ ├── cl_extern.h │ ├── cl_funcs.c │ ├── cl_main.c │ ├── cl_read.c │ ├── cl_screen.c │ ├── cl_term.c │ └── extern.h ├── common/ │ ├── args.h │ ├── common.h │ ├── cut.c │ ├── cut.h │ ├── delete.c │ ├── exf.c │ ├── exf.h │ ├── gs.h │ ├── key.c │ ├── key.h │ ├── line.c │ ├── log.c │ ├── log.h │ ├── main.c │ ├── mark.c │ ├── mark.h │ ├── mem.h │ ├── msg.c │ ├── msg.h │ ├── options.awk │ ├── options.c │ ├── options.h │ ├── options_f.c │ ├── put.c │ ├── recover.c │ ├── screen.c │ ├── screen.h │ ├── search.c │ ├── seq.c │ ├── seq.h │ └── util.c ├── db/ │ ├── btree/ │ │ ├── bt_close.c │ │ ├── bt_conv.c │ │ ├── bt_debug.c │ │ ├── bt_delete.c │ │ ├── bt_get.c │ │ ├── bt_open.c │ │ ├── bt_overflow.c │ │ ├── bt_page.c │ │ ├── bt_put.c │ │ ├── bt_search.c │ │ ├── bt_seq.c │ │ ├── bt_split.c │ │ ├── bt_utils.c │ │ ├── btree.h │ │ └── extern.h │ ├── db/ │ │ └── db.c │ ├── hash/ │ │ ├── bsd_ndbm.h │ │ ├── extern.h │ │ ├── hash.c │ │ ├── hash.h │ │ ├── hash_bigkey.c │ │ ├── hash_buf.c │ │ ├── hash_func.c │ │ ├── hash_log2.c │ │ ├── hash_page.c │ │ ├── ndbm.c │ │ └── page.h │ ├── mpool/ │ │ └── mpool.c │ └── recno/ │ ├── extern.h │ ├── rec_close.c │ ├── rec_delete.c │ ├── rec_get.c │ ├── rec_open.c │ ├── rec_put.c │ ├── rec_search.c │ ├── rec_seq.c │ ├── rec_utils.c │ └── recno.h ├── docs/ │ ├── USD.doc/ │ │ ├── edit/ │ │ │ ├── edit.vindex │ │ │ └── edittut.ms │ │ ├── exref/ │ │ │ ├── ex.rm │ │ │ └── ex.summary │ │ ├── re_format/ │ │ │ └── vi_regex.7 │ │ ├── vi.man/ │ │ │ └── vi.1 │ │ └── vitut/ │ │ ├── vi.apwh.ms │ │ ├── vi.chars │ │ ├── vi.in │ │ └── vi.summary │ ├── ev │ ├── ev.license │ ├── help │ ├── help.license │ ├── internals/ │ │ ├── autowrite │ │ ├── autowrite.license │ │ ├── context │ │ ├── context.license │ │ ├── gdb.script │ │ ├── input │ │ ├── input.license │ │ ├── openmode │ │ ├── openmode.license │ │ ├── quoting │ │ ├── quoting.license │ │ ├── structures │ │ └── structures.license │ └── tutorial/ │ ├── vi.advanced │ ├── vi.advanced.license │ ├── vi.beginner │ ├── vi.beginner.license │ └── vi.tut.csh ├── ex/ │ ├── ex.awk │ ├── ex.c │ ├── ex.h │ ├── ex_abbrev.c │ ├── ex_append.c │ ├── ex_args.c │ ├── ex_argv.c │ ├── ex_at.c │ ├── ex_bang.c │ ├── ex_cd.c │ ├── ex_cmd.c │ ├── ex_delete.c │ ├── ex_display.c │ ├── ex_edit.c │ ├── ex_equal.c │ ├── ex_file.c │ ├── ex_filter.c │ ├── ex_global.c │ ├── ex_init.c │ ├── ex_join.c │ ├── ex_map.c │ ├── ex_mark.c │ ├── ex_mkexrc.c │ ├── ex_move.c │ ├── ex_open.c │ ├── ex_preserve.c │ ├── ex_print.c │ ├── ex_put.c │ ├── ex_quit.c │ ├── ex_read.c │ ├── ex_screen.c │ ├── ex_script.c │ ├── ex_set.c │ ├── ex_shell.c │ ├── ex_shift.c │ ├── ex_source.c │ ├── ex_stop.c │ ├── ex_subst.c │ ├── ex_tag.c │ ├── ex_txt.c │ ├── ex_undo.c │ ├── ex_usage.c │ ├── ex_util.c │ ├── ex_version.c │ ├── ex_visual.c │ ├── ex_write.c │ ├── ex_yank.c │ ├── ex_z.c │ ├── script.h │ ├── tag.h │ └── version.h ├── include/ │ ├── bitstring.h │ ├── bsd_db.h │ ├── bsd_err.h │ ├── bsd_fcntl.h │ ├── bsd_regex.h │ ├── bsd_stdlib.h │ ├── bsd_string.h │ ├── bsd_termios.h │ ├── bsd_unistd.h │ ├── com_extern.h │ ├── compat.h │ ├── compat_bsd_db.h │ ├── ex_extern.h │ ├── libgen.h │ ├── mpool.h │ ├── pathnames.h │ ├── poll.h │ ├── sys/ │ │ ├── proc.h │ │ ├── queue.h │ │ ├── stat.h │ │ ├── time.h │ │ ├── tree.h │ │ └── types.h │ ├── util.h │ └── vi_extern.h ├── openbsd/ │ ├── basename.c │ ├── dirname.c │ ├── err.c │ ├── errc.c │ ├── errc.h │ ├── errx.c │ ├── getopt_long.c │ ├── getopt_long.h │ ├── getprogname.c │ ├── issetugid.c │ ├── minpwcache.c │ ├── minpwcache.h │ ├── open.c │ ├── pledge.c │ ├── reallocarray.c │ ├── setmode.c │ ├── setmode.h │ ├── strlcat.c │ ├── strlcpy.c │ ├── strtonum.c │ ├── verr.c │ ├── verrc.c │ ├── verrx.c │ ├── vwarn.c │ ├── vwarnc.c │ ├── vwarnx.c │ ├── warn.c │ ├── warnc.c │ └── warnx.c ├── regex/ │ ├── bsd_regex2.h │ ├── cclass.h │ ├── cname.h │ ├── engine.c │ ├── regcomp.c │ ├── regerror.c │ ├── regexec.c │ ├── regfree.c │ └── utils.h ├── scripts/ │ ├── virecover │ └── virecover.8 ├── vi/ │ ├── getc.c │ ├── v_at.c │ ├── v_ch.c │ ├── v_cmd.c │ ├── v_delete.c │ ├── v_ex.c │ ├── v_increment.c │ ├── v_init.c │ ├── v_itxt.c │ ├── v_left.c │ ├── v_mark.c │ ├── v_match.c │ ├── v_paragraph.c │ ├── v_put.c │ ├── v_redraw.c │ ├── v_replace.c │ ├── v_right.c │ ├── v_screen.c │ ├── v_scroll.c │ ├── v_search.c │ ├── v_section.c │ ├── v_sentence.c │ ├── v_status.c │ ├── v_txt.c │ ├── v_ulcase.c │ ├── v_undo.c │ ├── v_util.c │ ├── v_word.c │ ├── v_xchar.c │ ├── v_yank.c │ ├── v_z.c │ ├── v_zexit.c │ ├── vi.c │ ├── vi.h │ ├── vs_line.c │ ├── vs_msg.c │ ├── vs_refresh.c │ ├── vs_relative.c │ ├── vs_smap.c │ └── vs_split.c └── xinstall/ ├── xinstall.1 └── xinstall.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2021-2024 Jeffrey H. Johnson *.txt linguist-documentation *.ms linguist-documentation *.1 linguist-documentation *.8 linguist-documentation *.md linguist-documentation *.rm linguist-documentation *.advanced linguist-documentation *.beginner linguist-documentation *.summary linguist-documentation *.vindex linguist-documentation *.chars linguist-documentation *.refs linguist-documentation *.roff linguist-documentation ./docs/* linguist-documentation *.in linguist-vendored *.csh linguist-vendored *.script linguist-vendored ================================================ FILE: .gitignore ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2021-2024 Jeffrey H. Johnson # Prerequisites *.d # Timing *.t # Object files *.o *.ko *.obj *.elf *.dll *.exe # Linker output *.ilk *.map *.exp # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # Debug files *.dSYM/ *.su *.idb *.pdb # Kernel Module Compile Results *.mod* *.cmd .tmp_versions/ modules.order Module.symvers Mkfile.old dkms.conf # Output common/options_def.h ex/ex_def.h bin/* # Patch *.rej *.orig *.diff # Junk typescript .DS_Store .cproject .project .settings/ .vscode/ .nvimlog .vimlog *.*.swp test.txt # Tags tags .tags ctags .ctags etags .etags .TAGS TAGS GRTAGS GTAGS GPATH # Cores core *.core # Local .autoenv .envrc # Bad Targets all clean install distclean # Profiling *.gcda *.gcov *.gcno *.profdata *.profout # Tidy compile_commands.json ================================================ FILE: BSDmakefile ================================================ ############################################################################### # - O p e n V i - # ############################################################################### # vim: filetype=make:tabstop=8:tw=79:noexpandtab:colorcolumn=79 # SPDX-License-Identifier: BSD-3-Clause ############################################################################### ############################################################################### # # Copyright (c) 2021-2024 Jeffrey H. Johnson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered "AS-IS", # without any warranty. # ############################################################################### ############################################################################### # Configuration .SHELL: name=sh .MAIN: all .MAKE.JOBS ?= 1 .NOTPARALLEL: _FAIL all _GMAKE $(.TARGETS) .PHONY: _FAIL all _GMAKE $(.TARGETS) $(.TARGETS): _GMAKE ############################################################################### # Wrapper _GMAKE: @command -v gmake > /dev/null 2>&1 || \ { \ printf '\rError: %s\n' "GNU Make is required." 1>&2; \ exit 1; \ } && \ command gmake \ $$(printf '%s' "$(MAKEFLAGS)" 2> /dev/null | \ sed -e 's/-J .* //' \ -e 's/-J.* //' 2> /dev/null) \ $(.TARGETS) ############################################################################### ================================================ FILE: ChangeLog ================================================ OpenVi 7.7.32 -> OpenVi 7.8.33-dev: Wed Dec 24 15:06:57 2025 + Update README to mention optional `pkg-config` prerequisite + Do not include termio.h on Linux systems + Update email address (johnsonjh.dev@gmail.com) OpenVi 7.6.31 -> OpenVi 7.7.32: Thu Oct 2 03:49:26 2025 + Try to query `pkg-config` for ncurses flags and libs on some systems + Fix filename completion using `D_NAMLEN` as defined in the compat `include/compat.h` header; patch from @jerryfletcher21; closes #22. + Fix `p` command when used with a count + Fix crash with expandtab and running external commands; fix from Jerry Fletcher, OK job@ + Update docs to add percentage to ruler after recent changes OpenVi 7.6.30 -> OpenVi 7.6.31: Sun Apr 6 15:25:04 2025 + Don't require xinstall to be built for `install`, `strip`, and `sstrip` targets to complete successfully. + In xinstall's create_tempfile() pass pointer to full pathname to strlcat(); fixes a potential buffer overrun; also check strlcpy() and strlcat() return value to detect truncations; based on a diff from naddy@; ok naddy@ tb@ deraadt@ + Support building for Managarm (xbstrap cross-compilation only) OpenVi 7.5.29 -> OpenVi 7.6.30: Mon Oct 7 08:45:39 2024 + Bump for OpenBSD 7.6 release OpenVi 7.5.28 -> OpenVi 7.5.29: Tue May 21 05:26:38 2024 + Support building natively on OS/400; verified using PASE for IBM i 7.5 with GCC 10.5 (gcc10) and ncurses 6.0 (ncurses, ncurses-devel) + Add a new output level, M_XINFO, which outputs an informational message and ignores the state of the verbose and silent flags + Show version command output even in ex silent mode + Update the usage help text to document the new `-C` option + Minor changes to xinstall.c to appease Oracle Lint warnings + Consistently use .Dq for double-quoting in man pages + Fix various spelling errors and typos, mostly in comments + Increase buf size OpenVi 7.4.27 -> OpenVi 7.5.28: Sun Apr 7 19:06:02 2024 + Add `-C cmd` startup option; similar to `-c` but always runs `cmd` + Remove duplicate include statements from xinstall.c + Add showfilename set option to display file name; OK millert@ otto@ + Avoid use after free of frp and frp->tname; found by smatch, ok miod@ millert@ + fix fd leaks in error paths; ok miod@ OpenVi 7.4.26 -> OpenVi 7.4.27: Wed Nov 22 19:30:41 2023 + Update README file with additional links and packaging information + Always suppress db_err output when !lno; closes GitHub Issue #34 + Update minpwcache.c to OpenBSD pwcache.c v1.16; contains spelling fixes from Paul Tagliamonte; only comments, no user-facing change + Use openbsd_strlcpy for strlcpy in ptym_open(); fixes compilation on AIX and some other System V-derived systems OpenVi 7.4.25 -> OpenVi 7.4.26: Fri Oct 28 00:15:39 2023 + Add a fallback path for `altnotation` mode; fixes a crash on macOS + Bump MAX_CHARACTER_COLUMNS to 6 for future usage + Eliminate strcpy usage in ptym_open function OpenVi 7.4.24 -> OpenVi 7.4.25: Fri Oct 27 11:44:45 2023 + Add a new option, `altnotation` (abbreviation `an`), inspired by the Nvi2 2.2.1 option of the same name; if set, most control characters less than 0x20 will be displayed in notation, and carriage feed, escape, and delete will be displayed as , , and , respectively OpenVi 7.4.23 -> OpenVi 7.4.24: Tue Oct 10 19:47:07 2023 + Silence a warning when building with recent Clang compilers + Treat consecutive paragraph indicators as different paragraphs; Consecutive empty lines count toward the same state, so there're 2x states (to get in and out). `^L` and `.PP` are counted as text, hitting those in the text should be treated as getting out of a paragraph and then getting in. From Walter Alejandro Iglesias and Zhihao Yuan in nvi2; ok bluhm@ + Fix typo in last ChangeLog entry OpenVi 7.3.22 -> OpenVi 7.4.23: Sat Jun 24 03:12:41 2023 + Bump OpenBSD date synchronization version part to 06/23/2023 + Remove unused `__cur_db` variable in Berkeley DB code; ok millert@ + Spelling fixes (in comments only) for regex from Paul Tagliamonte + Remove vestigial `?` case from `xinstall` top-level `getopt` loop; Prompted by dlg@, help from dlg@, millert@; ok naddy@ millert@ dlg@ + Packaging improvements from @jswank to honor DESTDIR to support relocatable installation, use relative symlinks for `view` and `ex` programs, and not create `/var/tmp/vi.recover` during installation OpenVi 7.2.21 -> OpenVi 7.3.22: Tue Apr 11 07:54:09 2023 + OpenBSD 7.3 released; bumping OpenBSD major version part to 7.3 + Bump OpenBSD date synchronization version part to 01/29/2023 + Autoprint line number to match the confirmation line; patch from `nvi2` via millert@ + Fix crash when tags file pattern has a trailing `\`, patch via `nvi2`; co-authored by Craig Leres OpenVi 7.2.20 -> OpenVi 7.2.21: Tue Jan 31 03:26:07 2023 + Fix handling of ex_range escaped backslashes; If there are two consecutive backslashes, skip past both so the second is not mistakenly treated as an escape character. This is consistent with how escaped backslashes are treated in ex_substitute() and global(); from Bosco G. G. + Spelling fixes from Paul Tagliamonte + Fix ^^D and 0^D description in man page; pointed out by Tomas Rippl; ok jmc@ + Fix typo in `.gitattributes`; no code changes OpenVi 7.2.19 -> OpenVi 7.2.20: Sun Jan 1 05:36:31 2023 + Correctly increment version identifier to match release + Normalize comment blocks; no functional changes OpenVi 7.1.18 -> OpenVi 7.2.19: Tue Nov 29 22:04:43 2022 + Add translation of README.md into Brazilian Portuguese + Suppress the "UNLOCKED" message on AIX + Set OBJECT_MODE when stripping compiled binaries + Add DEP5 metadata file to the source tree + Add SPDX license identifiers for REUSE compliance + OpenBSD 7.2 enters beta; bumping OpenBSD version to 7.2 OpenVi 7.1.17 -> OpenVi 7.1.18: Sat Jul 23 19:40:59 2022 + Quiet complaints when recovery directory is non-existent + Improve various status messages + Add permission statements to `GNUmakefile`, `BSDmakefile` + Update documentation OpenVi 7.0.16 -> OpenVi 7.1.17: Sun Apr 24 07:57:29 2022 + In v_event_get check qp->output for NULL before passing to e_memcmp(); other users of qp->output already include a NULL check; avoids a crash when cursor key support is disabled in cl/cl_term.c; from Jeremy Mates; ok tb@ + Check tkp->output != NULL before taking strlen for both command mappings and input mappings; this adds a missing check for command mappings and simplifies the input mappings; ok millert@ + From upstream man page, add missing comma; ok jmc@ + Update documentation OpenVi 7.0.15 -> OpenVi 7.1.16: Sun Mar 13 22:10:37 2022 + Document that PCC (Portable C Compiler) is working + Add a BSD make wrapper that calls gmake if available + Add OpenBSD basename functionality for use with xinstall + Simplify compatibility headers + Make `sstrip` target depend on the `strip` target, since many platforms do not support all the sstrip options, so still stripping as much as is possible is the most user-friendly thing to try + Add support for Solaris; tested on Oracle Solaris 11.4.0 with GCC, Clang (V6+), and the Oracle Developer Studio V12.6 compiler + Add support for illumos; tested on OpenIndiana Hipster 2022.03 + Add support for NetBSD; tested on NetBSD/amd64 9.2-stable with the system provided GCC and Clang from binary packages; compiling `CC=clang LTO=1` requires the LLVM LLD linker `ld.lld` installed + Add support for IBM AIX 7+; tested on IBM AIX 7.2 and IBM AIX 7.3 with GNU GCC (8, 9, 10, 11), IBM XL C/C++ V16.1+ (gxlc, xlclang), and IBM Open XL C/C++ V17.1+ using ncurses from IBM's AIX Toolbox; AIX builds default to 64-bit (on 64-bit systems) when using a supported compiler; the environment variable `MAIXBITS` can be set to `32` or `64` to force compilation of a 32-bit or 64-bit binary + Various portability improvements (portable BSD getopt, warn, etc.) + Reorganize source tree to better separate logical components + `WCOREDUMP` is not in POSIX.1-2008, so don't require it to build + Similarly, check if `TIOCSCTTY` is available and don't require it + Add `xinstall`, a BSD install utility based on OpenBSD `install(1)` + Switch `vfork` to `fork` as `vfork` is now gone from POSIX.1-2008 + Clear pointer after ending screen for safety + `README.md`, documentation, and man page corrections and improvements + Apply `expandtab` to lines filtered with the `!` command, via `nvi2` + OpenBSD 7.1 enters beta; bumping OpenBSD version to 7.1 + Modify text `%s/OpenBSD vi/OpenVi/` in `common/recover.c` + Provide a more helpful message when ex-mode has background screens + Fully redraw screen on refresh with ^L / ^R + Make `taglength` work correctly; fix from nvi 1.8x + Make `join` work as specified in the POSIX standard; nvi 1.8x + Fix problem with autoindenting and ^^D input; patch from nvi 1.8x + Fix to reset screen offset of top line exceeding number of screens adapted from Sven Verdoolaege's nvi 1.8x patch + Fix tty from ex-mode on `q` when there are multiple screens; patch from Al Viro via nvi 1.8x; also minor redraw adjustments + Use `-Wall` by default for all builds; drop `-Wextra` for release + Switch default optimization level to `-Os` for release builds; use `-D_FORTIFY_SOURCE=2` for non-debugging builds by default as well + Do not warn about `ttyname` or `ioctl` failures for `stderr` unless `stderr` is a tty according to `isatty()` + Fix out of bounds access in file completion + Raise the maximum ex-script mmap size limit + `DEBUG=1` now defaults to `-O0` rather than `-Og` + Avoid undefined behavior in `vs_crel()` and `ex_is_abbrev()` + Suppress more warnings and potential warnings OpenVi 7.0.12 -> OpenVi 7.0.15: Thu Feb 24 12:18:33 2022 + Avoid O_PATH clash in source proper (rather than in awk script) + Remove deprecated interpreter internals documentation + Actually use `pledge()` for SerenityOS and OpenBSD 5.9 or later + Add `superstrip` (aka `sstrip`) `GNUmakefile` target to aggressively strip the compiled binary using `sstrip` if available + Add `upx` `GNUmakefile` target to compress the compiled binary using `upx` if available + Cosmetic clean-up of `GNUmakefile` and normalize to 79-columns + MSYS2 is also supported, tested with MSYS2 on Windows 11 (x86_64) + Add support for Cygwin, tested with Cygwin64 on Windows 11 (x86_64) + Update `README.md` to document usage for `LIBS` variable, correct `OPTFLAGS` to `OPTLEVEL`, expand external links and information, add citations regarding past multibyte efforts, use fancy quotes, and correctly state that the traditional `ex` / `vi` was part of the *first* Berkeley Software Distribution, mention OpenBSD's standard secure coding practices, safe(r) functions, and ISC license + Respect `LIBS` to set/override the default libraries for linking + Make failure to strip non-fatal; fixes `install-strip` and `strip` in the case where `./bin/vi` is un-strippable (i.e. `bin/vi` is `upx` compressed or missing section headers from `sstrip`ing) + Fixes for `vi` recovery mode. From `trondd@`, tested by various, ok `afresh1@`; this advances OpenBSD release date to 02/20/2022 + Update `.gitignore` to add `compile_commands.json` + Suppress a few possible warnings + Since HiDPI screens are more common, allow terminal dimensions of 3640x2048; this might need to be further extended for 4K displays OpenVi 7.0.11 -> OpenVi 7.0.12: Sun Feb 20 12:57:42 2022 + Update `README.md` with some background info and rationale + Always use internal reallocarray; closes GitHub Issue #5 OpenVi 7.0.10 -> OpenVi 7.0.11: Sat Feb 12 19:42:28 2022 + Silence a few more warnings; adjust default debugging `CFLAGS` + Add an initial regex man page (which is not yet installed), `docs/USD.doc/re_format/vi_regex.7` + Update `LICENSE.md`; fix email address for `millert@` + Simplify `db/sys/issetugid.c` ifdef's + Update bundled *OpenBSD* cclass.h to `1.7` (2020/12/30 08:54:42) + Update bundled *OpenBSD* cname.h to `1.6` (2020/12/30 08:53:30) + Update bundled *OpenBSD* engine.c to `1.26` (2020/12/28 21:41:55) + Update bundled *OpenBSD* regerror.c to `1.15` (2020/12/30 08:56:38) + Update bundled *OpenBSD* regex2.h to `1.12` (2021/01/03 17:07:58) + Update bundled *OpenBSD* regexec.c to `1.14` (2018/07/11 12:38:46) + Update bundled *OpenBSD* regcomp.c to `1.43` (2021/01/03 17:07:57) + Update bundled *OpenBSD* strlcpy to `1.16` (2019/01/25 00:19:25) + Update bundled *OpenBSD* reallocarray to `1.3` (2015/09/13 08:31:47) + Update bundled *OpenBSD* getopt_long to `1.32` (2020/05/27 22:25:09) + Update bundled *OpenBSD* basename to `1.17` (2020/10/20 19:30:14) + Add initial *Midipix* support OpenVi 7.0.9 -> OpenVi 7.0.10: Wed Feb 9 16:36:25 2022 + Silence some warnings and general style clean-up + Clarify text of some visual mode messages; use the POSIX thousands numeric separator for displaying most line and character counts + Use `/var/tmp` for the `vi.recover` directory; on most Linux systems, this persists across reboots and gives a higher chance of recovery than `/tmp` which is often a memory-backed filesystem + New feature: If `ruler` is set, print a percentage after position + New feature: If `windowname` is set, always show the file name + New feature: If `bserase` (abbrev `bse`) is set, then any newly backspaced characters are immediately erased from the screen + Handle `SIGQUIT` like `SIGINT`, which prevents `^\` (Control-\) from causing ex-mode to abort and drop core; aligns closer to Vim's behavior in ex-mode - which uses non-canonical input mode, so `^\` doesn't cause a stop. The rationale for this change is because we're using `^\` as a shortcut to enter our ex-mode (which for `nex` uses standard canonical line-based input), if `SIGQUIT` is unhandled, it's easy to abort by accident by sending more than one `^\` in succession, which can happen on lagged connection + Fix a long-standing bug where using `^G` on an empty file would include a random garbage byte in the displayed output + Reword "Already in the first column" to "Already at the first column" to be consistent with the other movement error messages + Remove documentation files not explicitly under the 3-BSD license + Update header comment of all modified files + Reformat the ChangeLog to break lines at 76 columns + Convert tabs to spaces and adjust source indentation with `cppi` + Update docs; Markdown-ify `LICENSE` and rename to `LICENSE.md` OpenVi 7.0.8 -> OpenVi 7.0.9: Mon Jan 31 21:43:08 2022 + Add a missing include to fix compilation on Void with musl libc + Update `README.md` OpenVi 7.0.7 -> OpenVi 7.0.8: Mon Jan 31 10:12:53 2022 + Rework `README.md` + Drop `-pipe` and `-fomit-frame-pointer` for wider compatibility + Add support for building on macOS; tested on 12.1/x86_64/21C5021h + Add support for building on OpenBSD; tested on 7.0-current/ARM64 + Normalize licensing, acknowledgements, and copyright statements + Normalize macros and conditional if/ifdef/ifndef's with `cppi` + Add support for building on FreeBSD; tested on 13.0-REL-p6/ARM64 + Minor code clean-up + Drop memmove wrapper (for old systems with bcopy but no memmove) + Add support for building with musl libc + Add `imctrl` and `imkey` options, inspired by `cannactrl` and `fepkey` options in `nvi-m17n` by itojun. If `imctrl` option is set, the input method is controlled by using escape sequences compatible with Tera Term and RLogin. The state of the input method in commands specified by imkey option is saved and restored automatically. This input method is deactivated upon returning to command mode; this implementation taken from NetBSD-current's `contrib/nvi` + Improve `make` output and use more logical compilation order + Add `virecover.8` man page adapted from NetBSD + Add a missing break in `common/log.c` + Avoid undefined behavior in `*BIT` macros; patch from NetBSD; also apply patch from NetBSD for PR bin/52716 + Update `.gitignore` and `.gitattributes` OpenVi 7.0.6 -> OpenVi 7.0.7: Fri Jan 28 16:33:22 2022 + Important fix to `GNUmakefile` for linking with Clang's `lld` + Remove installed man pages as part of the `uninstall` target + Workaround to accommodate `make clean all -j N' where `N` > 1 + Clean-up whitespace and line lengths of sources and `GNUmakefile` OpenVi 7.0.5 -> OpenVi 7.0.6: Thu Jan 27 16:45:12 2022 + Important fix for back-end database file locking; also, switch back to using the system libc-provided mkstemp functions to elide some issues that would otherwise occur on networked filesystems + Add proper attribution for DragonflyBSD Project to `LICENSE` text + Correct `GNUmakefile` messages for the `make install` target + Clean-up excess and trailing whitespace in source files OpenVi 7.0.4 -> OpenVi 7.0.5: Thu Jan 27 11:00:46 2022 + No verbose build by default; set variables V (or DEBUG) to enable + No LTO and LGC by default; set variables LTO and/or LGC to enable + Don't attempt linking with libjemalloc or libmtmalloc by default + Clean-up headers to speed compilation time & decrease binary size + Import and adapt OpenBSD Berkeley DB from OpenBSD 7-current libc + Remove the unused "maintainer-clean" target from GNUmakefile + Build against the newly bundled OpenBSD Berkeley DB by default; the imperfect Berkeley DB 3/4/5 support will remain available OpenVi 7.0.3 -> OpenVi 7.0.4: Wed Jan 26 13:09:45 2022 + New feature: "set visibletab" (abbrev "set vt") which toggles displaying tabs visibly while editing, useful for Makefiles, etc + Decrease the width of the line-number column by one row + Increase the length of the divider decoration string (by 2x) + Improve the GNUmakefile to avoid remaking any non-stale targets + Portably seed the standard random number generator OpenVi 7.0.2 -> OpenVi 7.0.3: Wed Jan 26 08:31:28 2022 + GNUmakefile "install" target now installs docs (man pages) + Remove 'docs/internals/cscope.NOTES' and documentation references + General build, portability, and code readability improvements + Relicense new contributions under the same 3-clause BSD license used by the overall project and update LICENSE file text + Remove unused headers and legacy documentation from the sources + Add standalone "strip" target to GNUmakefile which strips the uninstalled compiled binary + Set visible tab character to '~' and expose option in GNUmakefile + Installed binaries are prefixed with 'o' (e.g. 'ovi') by default + Explicitly invoke the POSIX-compliant version of the awk utility + Clarify (and simplify) GNUmakefile and build configuration + Update ChangeLog and docs for consistency, fix spelling and typos + Update LICENSE text OpenVi 7.0.1 -> OpenVi 7.0.2: Tue Jan 25 13:44:48 2022 + Debugging builds now default to compiling with '-Wall -Wextra' + Only pass '-pipe' to the compiler for non-debugging builds + Expand the formatting of the usage help text for readability + Change the DEBUG_VI GNUmakefile flag to simply DEBUG + For debug builds, document the existence of -T (Trace) in usage + Don't strip the binary by default; add "install-strip" target + Make clean targets more robust; warn if unable to remove 'bin' + Add standard "clean", "distclean", "realclean", "mostlyclean", and "maintainer-clean" targets to GNUmakefile + Increase the default escapetime to 2/10ths of a second + Add OpenBSD-compatible mkstemp function allowing for longer names + Silence some potential warnings with explicit casting + Exit with an error when screen is too small for visual mode + Apply Debian's patch to fix backwards sentence moving + Fix horizontal scroll count; patch from Debian + Change a #define to a typedef in regex library + Fix some typos and minor errors in the documentation and tutorials + Update .gitignore to include patch/diff detritus; no binary change + Adjust calculation to avoid "BDB0511 page sizes must be a power-of-2" warning; BDB 1.85 only cared if page size was even; closes GH issue #3 + Apply .exrc writeability patch from hesso at pool.math.tu-berlin.de + If TERM is unset, set to NULL, or otherwise unknown, first attempt to fallback to "vt100"; exit with a fatal error if "vt100" fails + Disallow pattern spaces which would cause intermediate calculations to overflow size_t. (CERT: VU#695940) + Bump version and use -dev to denote development version + Reformat ChangeLog for consistency; various spelling corrections + Update ChangeLog with OpenVi history + Format OpenBSD commits to ChangeLog format + Clean-up of legacy documentation; adjust formatting + Simplify GNUmakefile rules + Update GNUmakefile; correct a typo in a message + Update LICENSE text and formatting OpenBSD 7.0 -> OpenVi 7.0.1: Mon Jan 24 10:37:28 2022 + OpenVi created supporting glibc-based Linux systems + Create GNUmakefile and adapt build system + Attempt to detect and use mtmalloc or jemalloc if available + Support builds with post-1.8.5 BerkeleyDB versions (using BDB 1.8.5 emulation) but at the expense of preservation and recovery + Create initial README.md stub Mon Oct 25 14:17:24 2021 + vi(1): fix use after free with unsaved buffer. Issuing a zero-arg ex_edit command (:e) while using a named buffer with no backing file caused vi(1)/ex(1) to free the strings representing the buffer name and the name of the temporary file. This change detects the situation and only frees the newly allocated EXF structure (ep). Reported on bugs@ by kn@. OK millert@ Sun Oct 24 21:24:15 2021 + For open/openat, if the flags parameter does not contain O_CREAT, the 3rd (variadic) mode_t parameter is irrelevant Many developers in the past have passed mode_t (0, 044, 0644, or such), which might lead future people to copy this broken idiom, and perhaps even believe this parameter has some meaning or implication or application. Delete them all. This comes out of a conversation where tb@ noticed that a strange (but intentional) pledge behavior is to always knock-out high-bits from mode_t on a number of system calls as a safety factor, and his bewilderment that this appeared to be happening against valid modes (at least visually), but no sorry, they are all irrelevant junk. They could all be 0xdeafbeef. ok millert@ OpenBSD 6.9 -> OpenBSD 7.0: Thu Sep 2 11:19:02 2021 + Make all signal handler functions async-signal-safe by deleting the redundant "killersig" struct member and using the existing sig_atomic_t cl_sigterm variable instead + While here, garbage collect the h_hup() signal handler which is essentially identical to h_term(). This also gets rid of the last #define & #undef in cl_main.c Wed Sep 1 14:28:15 2021 + As a first step towards safe signal handling, improve the h_int() and h_winch() signal handlers to make one single store to a sig_atomic_t variable. Note that the h_hup() and h_term() signal handlers are still unsafe after this commit because they also set the "killersig" (how fitting!) field in a global struct OpenBSD 6.8 -> OpenBSD 6.9: Tue Apr 13 15:39:21 2021 + Require that the argument to the window option be non-zero. A zero-row window would not be usable (no room to edit) and the code is full of assumptions that "sp->t_rows - 1" >= 0. From Erik Ruotsalainen, fixes a bug reported by Paul de Weerd + Ignore expandtab setting when in command mode. Fixes things like searching for a literal tab character when expandtab is enabled; from `nvi2` (leres); OK martijn@ Mon Mar 8 02:47:25 2021 + Add some references, most of these were removed when we stopped building and installing USD/SMM/PSD docs OpenBSD 6.7 -> OpenBSD 6.8: Tue Jan 26 18:19:43 2021 + Satisfy -fno-common by repairing one enum decl; ok mortimer@ OpenBSD 6.6 -> OpenBSD 6.7: Sun May 24 13:33:55 2020 + Correct one statement about how the 'm' command works, talk less about ancient terminals, employ the more usual term "caret" rather than "up-arrow", and fix a few minor nits OpenBSD 6.5 -> OpenBSD 6.6: Thu Apr 30 10:40:21 2020 + Add an expandtab option, similar to what vim supports. If set, expands tabs to spaces in insert mode as well as when shifting and indenting/outdenting. If quoted with ^V, a literal tab is inserted. Adapted from NetBSD, but this implementation more closely matches vim's behavior. OK dlg@ Fri Oct 4 20:12:01 2019 + Better link "set" and "SET OPTIONS"; original diff from sven falempin, tweaked a bit by myself Mon Jul 22 12:39:02 2019 + In secure mode (-S), skip sending mail when executing the :pre[serve] command or when dying from SIGTERM. This way, creating the recovery file works again without re-adding "proc exec" to the pledge(2). As reported by Jesper Wallin , this got broken by common/main.c rev. 1.29 (Nov 19, 2015). The general direction of the fix was suggested by brynet@. OK brynet@ and no opposition when shown on tech@ OpenBSD 6.4 -> OpenBSD 6.5: Tue May 21 09:24:58 2019 + Also apply stricter pledge when secure mode is set via rc file or command OpenBSD 6.3 -> OpenBSD 6.4: Thu Jan 24 15:09:41 2019 + Fix a crash on long lines when switching to another file by setting SC_SCR_CENTER which will cause the offsets in HMAP to be reset when painting the screen. OK martijn@ otto@ Mon Sep 17 15:41:17 2018 + Use the strict pragma for better warnings Fri Jul 13 20:06:10 2018 + Remove Cscope leftover and a stray comma + Unused variable Wed Jul 11 06:39:23 2018 + Remove an old (and false) comment. REALLOC now free(3)s the code if realloc fails OpenBSD 6.2 -> OpenBSD 6.3: Mon Feb 12 01:10:46 2018 + Simplify documentation of split-screen mode, avoiding abuse of [] to sometimes mean "character set", which conflicts with the normal meaning of "optional element" in manual pages + While here, add a few related clarifications and tweak a few details. Triggered by a minor bug report from , and by bentley@ subsequently pointing out the abuse of []. Patch using input from jmc@, who also agreed with some previous versions Sat Feb 3 15:44:36 2018 + The recover script should have the same sanity checks as recover.c. Specifically, open files with O_NONBLOCK and enforce a mode of 0600 Thu Dec 14 10:02:53 2017 + Enable the awk scripts to generate ex_def.h and options_def.h. These scripts generate the enums required for the ex commands and vi options. Before these lists had to be maintained either by hand or someone had to stumble upon these scripts and figure out how to use them. By enabling them these headers are now always in sync based on the comments in the corresponding source files, which are a lot harder to miss during an update than an extra file Sun Nov 26 09:59:41 2017 + Fix segfault which could be triggered by deleting a backwards sentence if cursor's current line was blank: Fri Nov 10 18:31:36 2017 + When tracing is compiled in make sure it flushes its content to disk as soon as the TRACE function is called. This helps while debugging crashes + Fix a use after free when sending SIGHUP or SIGTERM to vi when in editing mode + Add rcv_openat() function that does the open, makes sure it is a regular file with the expected permissions and locks it Inspired by changes in NetBSD by Christos. OK martijn@ + Avoid using system(3) when running "sendmail -t". We already have the recover file fd open so just run sendmail with stdin set to the recover file. OK martijn@ OpenBSD 6.1 -> OpenBSD 6.2: Tue Aug 22 20:27:18 2017 + Do not treat comma as part of the command modifier Mon Jul 31 19:45:49 2017 + Silence some warnings generated by clang. Original diff by espie@ with some minor tweaks by myself Thu Jul 20 08:37:48 2017 + Replace usage of strtol() with strtonum() Wed Jul 5 18:56:33 2017 + Avoid double space caused by end-of-sentence detection requested by jmc@ + Nits about trailing punctuation found with mandoc -Tlint Mon Jul 3 14:30:11 2017 + Markup fixes + Remove settings that were unimplemented for 20 years; update STANDARDS Fri Jun 30 14:42:05 2017 + Add mdoc(7) macros to vi's built-in lists of roff paragraph/section macros Sat Jun 24 16:30:47 2017 + Fix a check in ADD_SPACE_{GOTO,RET} that potentially allowed for a NULL-dereference Tue Jun 20 07:32:56 2017 + Better document the :s ex command and its variants Thu Jun 15 06:44:47 2017 + "10th's of a second" -> "tenths of a second" Mon Jun 12 18:38:57 2017 + Use openat() and unlinkat() instead of chdir()ing to the recovery dir. Since we use flock() and not fcntl() locking we can open the recovery file read-only. OK martijn@ Wed Apr 26 13:14:28 2017 + Remove extraneous ", NULL" in the assignment of msgstr which was leftover from when msg_cat() was removed. From Anton Lindqvist Tue Apr 18 01:45:33 2017 + free(NULL) is ok so use it; from Michael W. Bombardieri OpenBSD 6.0 -> OpenBSD 6.1: Fri Jan 20 00:55:52 2017 + Nuke some excess whitespace Sun Dec 18 18:28:38 2016 + Use %zu/%d to print size_t/ssize_t. Cast recno_t (a.k.a. u_int32_t) to (unsigned long) to match %lu formats. Makes gcc happier and quieter + Nuke more unused variables Sat Nov 5 16:21:56 2016 + Remove syscall.ph from vi.recover Fri Sep 2 15:38:42 2016 + Fix the begin of word issue in vi(1). Similar fix went in sed and ed OpenBSD 5.9 -> OpenBSD 6.0: Sat Aug 27 04:07:42 2016 + Pull in for struct timespec and timeval Sun Aug 14 21:47:16 2016 + Kill '#if defined(DEBUG) && 0' blocks that used %q Mon Aug 8 15:09:32 2016 + /tmp and /var/tmp are the same, consistently use the former in both build/recover and documentation Mon Aug 1 18:27:35 2016 + Remove vi's "directory" option and TMPDIR support Thu Jul 7 09:26:25 2016 + biff, mesg, vi: only consider ACCESSPERMS for setting tty mode Wed Jun 29 20:38:39 2016 + If /tmp/vi.recover doesn't exist, don't create it. Warn once that it doesn't exist, afterwards fail silently Sat May 28 18:30:35 2016 + Test if stdin is a terminal before resetting the tty state. Diff supplied by Kai Antweiler Fri May 27 09:18:11 2016 + Revert CHAR_T removal. Some signedness flaws were introduced. Found the hard way by jca@ Sat May 7 14:03:01 2016 + Free memory if realloc fails. The application is most likely to terminate after a failure, but if it does not we better clean up after ourselves Thu May 5 20:36:41 2016 + Remove __sigblockset. This is a leftover after the removal of the signal blocking code in common/gs.h rev 1.14 Mon May 2 20:51:35 2016 + Remove pointless comment. getcwd(3) is safe + Remove CHAR_T in favor of native types Wed Apr 20 19:34:32 2016 + Remove pointless reenter variable Tue Apr 19 17:42:09 2016 + Remove not implemented declaration of sscr_pty + Remove some useless code Wed Mar 30 06:38:40 2016 + For some time now mandoc has not required MLINKS to function correctly; logically complete that now by removing MLINKS from base OpenBSD 5.8 -> OpenBSD 5.9: Sat Mar 19 00:21:28 2016 + By issuing :e +something in vi(1) this uncovers a backwards memcpy with the code because the 2 buffers overlap and in order to solve it then replace memcpy(3) call by memmove(3) Thu Mar 17 03:44:05 2016 + Add error checking for COLUMNS/LINES environment variables Sun Mar 13 18:30:43 2016 + Remove an extra space before ^\ help message. Fixes alignment in viusage Thu Feb 11 16:34:12 2016 + Update comment: the #ifdef VDSUSP was removed in r1.22 Tue Feb 9 07:41:12 2016 + Avoid special characters; from Michael Reed Wed Feb 3 01:47:25 2016 + Remove needless alias macros for malloc and calloc. No binary change. I got this up-streamed a few weeks ago Sat Jan 30 21:34:57 2016 + /var/tmp is dead, long live /tmp + Replace tail with basename + Replace progname variable in gs structure with getprogname Wed Jan 27 22:46:02 2016 + remove v_estr in favor of warn and warnx Wed Jan 27 22:38:12 2016 + Replace fprintf+exit with errx. No functional change Wed Jan 20 08:43:27 2016 + Remove ARG_CHAR_T, a relic from when the code was written K&R style Sat Jan 9 16:13:26 2016 + decls before code; from Martijn van Duren Wed Jan 6 22:46:59 2016 + Remove mention of message catalog dir + We don't use configure so this file is full of lies and we are better off without it + Remove msgcat from the documentation + Remove prototype for now-deleted f_msgcat() + Remove the actual message catalogs. From Martijn van Duren + Remove the msg_cat() function and adjust its former callers. From Martijn van Duren + Remove the numeric identifiers at the beginning of the messages which used to be used as the message number to lookup in the catalog. From Martijn van Duren + Remove the message catalog DB. This removes the msg_open() and msg_close() functions along with the msgcat command. From Martijn van Duren Mon Dec 28 19:24:01 2015 + Use err() instead of custom perr() function. Also applied by `nvi2` upstream; From Martijn van Duren Mon Dec 7 20:39:19 2015 + Remove needless type casts and corresponding type parameters from allocation macros. No binary change Thu Dec 3 08:13:15 2015 + After inserting a backslash, don't treat ^H ^? or ^U as special cases. These days, ^V to escape is a universal feature and needing two keystrokes to delete backslashes is really annoying Tue Nov 24 12:56:31 2015 + Update the other documentation to match the new filec default + Turn on filename tab completion in vi by default Mon Nov 23 09:03:01 2015 + Remove Cscope references in documentation Fri Nov 20 04:12:19 2015 + vi -S doesn't need proc or exec Thu Nov 19 19:30:44 2015 + "tty proc exec", not "proc exec tty" + Remove Cscope support in vi OpenBSD 5.7 -> OpenBSD 5.8: Sun Nov 15 01:22:36 2015 + Vi needs flock, for those who haven't set nolock in .exrc for years + Basic pledge for vi Mon Sep 14 20:06:58 2015 + Avoid .Ns right after .Pf, it's pointless + In some cases, do additional cleanup in the immediate vicinity Tue Jul 7 18:34:12 2015 + Fix a regression caused by timespec changes when vi is run without a file to edit. Based on a diff from Patrick Keshishian OpenBSD 5.6 -> OpenBSD 5.7: Fri Apr 24 21:48:31 2015 + struct timespec/clock_gettime(3) conversion for vi(1) Tue Apr 21 01:41:42 2015 + init both fds passed to pipe as -1 instead of init'ing one twice ok deraadt@ guenther@ miod@ millert@ Sun Apr 19 01:10:59 2015 + Don't lock the file for "vi -R" or "view". OK deraadt@ Fri Apr 10 18:05:51 2015 + This changes vi to use resizeterm(3) instead of re-initializing curses on window re-sizes, which was leaking massive amounts of memory Sun Mar 29 01:04:23 2015 + Remove SA_INTERRUPT, HISTORIC_PRACTICE, and HISTORICAL_PRACTICE using unifdef. It seems clear that no one was using these (SA_INTERRUPT didn't even build the other way). Tweak comments as appropriate Sat Mar 28 12:54:37 2015 + vi was using two separate isblank functions: one defined in and the other #defined in common/key.h. There is no reason to have both. For consistency use the isblank function from , remove the #define in common/key.h, and add #include to the files that were missing the header Fri Mar 27 04:11:25 2015 + Some vi cleanup, unifdef's some signal blocking code that has never been enabled in our tree, also removes some stragglers from a global struct referencing nonexistent Tcl/TK and "IP support". And finally.. delete an empty file missed by earlier cleanup by bentley@ Tue Mar 17 10:08:18 2015 + Don't use the wrong escape for < and >. Tweak wording to match the page Fri Mar 13 19:58:40 2015 + Remove the first comma from constructs like ", and," and ", or,": you can use "and" and "or" to join sentence clauses, and you can use commas, but both hinders reading Tue Mar 10 00:10:59 2015 + Display "Search wrapped" even when searching from the end of the file Sat Feb 28 21:51:56 2015 + Reduce usage of predefined strings in man pages Fri Feb 6 22:29:31 2015 + Do not rely on unspecified behavior for the size_t overflow check. OK miod@ Fri Jan 16 06:39:28 2015 + Replace with and other less dirty headers where possible + Annotate lines with their current reasons + Switch to PATH_MAX, NGROUPS_MAX, HOST_NAME_MAX+1, LOGIN_NAME_MAX + Change MIN() and MAX() to local definitions of MINIMUM() and MAXIMUM() where sensible to avoid pulling in the pollution. These are the files confirmed through binary verification. ok guenther@, millert@, doug@ (helped with the verification protocol) Thu Nov 20 08:50:53 2014 + Remove the vi Perl API Wed Nov 19 03:42:40 2014 + Remove ifdef checks for LIBRARY. It is undocumented and triggers the same conditional inclusions as PURIFY does Fri Nov 14 20:27:03 2014 + _PATH_BSHELL, _PATH_SENDMAIL, _PATH_TMP and _PATH_TTY are defined in and _PATH_SYSV_TTY is unused. All of them can be removed from pathnames.h. The other defines can be made unconditionally + The 'tcl' command in vi does nothing, except to print the message "Vi was not loaded with a Tcl interpreter". Printing the standard message for unknown commands would be equally descriptive with the benefit of reducing code size + The vi editor contains code for two different file locking methods; one using flock(), the other using fcntl(). The fcntl method is unused and has severe limitations (as described in a code comment); removed it for sake of readability Wed Nov 12 16:29:04 2014 + Remove more portability bits for older systems; from Martin Natano + ANSIfy vi Mon Nov 10 21:40:11 2014 + Remove various bits of autoconf cruft. from Martin Natano + Remove ipc leftovers. from Martin Natano + Remove old, unnecessary compat code. from Martin Natano OpenBSD 5.5 -> OpenBSD 5.6: Thu Nov 6 11:35:02 2014 + Clean up unused header files and docs referring to them + Remove old curses support in vi OpenBSD 5.4 -> OpenBSD 5.5: Tue Oct 14 22:23:12 2014 + Create a REALLOCARRAY macro, and use it where it gives us overflow protection for free ok guenther@ Wed Oct 8 00:53:45 2014 + Bump max columns out to 768 since screens are getting bigger Tue Sep 9 14:10:35 2014 + We no longer need to convert "\<" and "\>" to "[[:<:]]" and "[[:>:]]" respectively now that the former is natively supported OK jsg@ Thu Jul 10 20:33:42 2014 + Add missing include file to bring in protos Sun Dec 1 20:22:34 2013 + Change the file reference queue from CIRCLEQ to TAILQ + Change the tags queue from CIRCLEQ to TAILQ + Change the tag queue from CIRCLEQ to TAILQ + Convert the ranges CIRCLEQ to TAILQ Thu Nov 28 22:12:40 2013 + Convert the display screens and hidden screens CIRCLEQ's to TAILQ's Wed Nov 27 08:52:41 2013 + Zap some pointer casts became extra (and thus dangerous) after recent CIRCLEQ removal Tue Nov 26 17:48:01 2013 + Fix a possible double-free/NULL deref in msg_print + Tweak a tortuous manual loop into a TAILQ_FOREACH() + Fix condition after CIRCLEQ -> TAILQ conversion; ok zhuk@ + Fix incorrectly converted CIRCLEQ_END comparison to prevent NULL deref's Mon Nov 25 23:27:11 2013 + Replace _texth CIRCLEQ with TAILQ. One down, five to go OpenBSD 5.3 -> OpenBSD 5.4: Thu Aug 22 04:43:40 2013 + Correct format string mismatches turned up by -Wformat=2 Sat Jun 22 18:52:52 2013 + Tweak optimization flags on landisk until I have time to investigate further Tue May 14 11:51:41 2013 + When ^W (WERASE) is hit in insert mode it's possible that the line buffer is accessed out of bounds. If 'max' == 0 and 'tp->cno' == 1 the 'tp->cno' value is first reduced by one and then 'tp->lb' is accessed at 'tp->cno' - 1. Also remove dead (and incorrect) code in the TXT_ALTWERASE case; from Arto Jonsson OK martynas@ OpenBSD 5.2 -> OpenBSD 5.3: Fri May 3 20:43:25 2013 + Use open(2) / fstat(2) instead of stat(2) / open(2) for checking proper permissions of "local" .exrc or .nexrc files Mon Apr 29 00:28:23 2013 + Use FD_CLOEXEC instead of 1; from David Hill Thu Dec 20 20:28:12 2012 + Use openpty() rather than hand-rolled pty opening code ok millert@ OpenBSD 5.1 -> OpenBSD 5.2: Mon Dec 3 22:05:46 2012 + Fix hang when exiting shell in script mode. OK naddy@ OpenBSD 5.0 -> OpenBSD 5.1: Tue Jan 17 08:18:36 2012 + Flesh out the VI COMMANDS section somewhat; diff from Alexis Fouilhe; help; ok sobrado@ OpenBSD 4.9 -> OpenBSD 5.0: Wed Dec 28 01:52:33 2011 + These utilities were already part of 1BSD, and some authors are known. All facts from the CSRG archive CD 1, also available from minnie.tuhs.org. Feedback and OK sobrado@, ok jmc@ Fri Jul 29 13:24:50 2011 + Document vi/ex regular expressions, and where they differ from those documented in re_format(7) Sun Jul 10 13:20:25 2011 + Rename O_DIRECTORY to O_TMP_DIRECTORY to avoid a namespace collision with sys/fcntl.h. OK deraadt@ Mon May 16 16:41:58 2011 + Better document some of the terminology used in the VI COMMANDS section; from Alexis Fouilhe OpenBSD 4.8 -> OpenBSD 4.9: Mon May 2 11:14:11 2011 + No need to escape `|'; as discussed with schwartze@ Tue Apr 12 18:08:00 2011 + Better document vi's startup (in terms of environment variables and config files) Sun Apr 10 21:21:50 2011 + Fix display glitch leading to crash. If we're reformatting, check the screens necessary to display the line and modify head or tail of the smap accordingly; since it might have changed due to e.g. smaller tabstop value Thu Mar 31 20:40:51 2011 + Add a BUFFERS section, to explain how they work; from Alexis Fouilhe - many thanks to him for his work on this Thu Mar 17 11:34:53 2011 + since we stopped installing the USD docs, it no longer makes sense for DESCRIPTION to point to SEE ALSO OpenBSD 4.7 -> OpenBSD 4.8: Wed Jan 5 14:01:32 2011 + Fix typo, PR#6538 Mon Oct 18 14:42:16 2010 + Remove references to now removed USD/PSD/SMM docs Sun Oct 17 22:54:37 2010 + Stop installing me(1) and ms(1) source code. We will soon get rid of groff in base, so there is no longer any way to use these files with base. No opposition on tech@ Wed Sep 29 07:44:56 2010 + Various EXIT STATUS fixes; from Daniel Dickman Fri Sep 24 06:40:12 2010 + Add a little padding to make SYNOPSIS line up nicely Sun Jul 25 20:23:41 2010 + ^U scrolls backwards, not forwards; from marrob at lavabit com Sun Jul 18 21:45:01 2010 + Remove some nasty hacks Thu Jul 15 20:51:38 2010 + More delimiters that need quoting inside macros, hunted down by jmc@, who asked me to commit because he is just running out of the door OpenBSD 4.6 -> OpenBSD 4.7: Sat May 29 06:40:00 2010 + subsitution -> substitution; from Yoshihiro Ota, FreeBSD PR#130874 Sun Nov 22 22:51:58 2009 + Fix for flash defaulting to off, pointed out by jmc@ + Change the flash option to be off by default. Now that xterm has the flash capability in terminfo, vi was using it instead of beeping, but it is too slow for some machines Sun Nov 15 04:32:31 2009 + Do not leak a lot of memory if a small memory allocation fails, found by parfait@ ok kettenis@ guenther@ Sat Nov 14 17:44:53 2009 + Fix leaks in error paths found by parfait ok deraadt@ Tue Oct 27 23:59:19 2009 + Remove rcsid[] and sccsid[] and copyright[] that are essentially unmaintained (and unmaintainable). These days, people use source These id's do not provide any benefit, and do hurt the small install media (the 33,000 line diff is essentially mechanical); ok with the idea millert@, ok dms@ OpenBSD 4.5 -> OpenBSD 4.6: Tue Oct 20 09:54:47 2009 + ex(1) and vi(1) are different editors. The diff is based on the original printed edition of the User's Reference Manual from USENIX and O'Reilly. 4.4BSD had exactly this, that is much more accurate than our current description (while here, Jason observed that both FreeBSD and NetBSD do the same) Wed Jun 10 14:03:18 2009 + Use poll() instead of select(). The sscr_check_input() bit is adapted from nvi 1.81. Tested by several people during the hackathon OpenBSD 4.4 -> OpenBSD 4.5: Tue Jun 2 00:21:32 2009 + If the read from the tty fails with EAGAIN, pop back up to the select. Seems to happen occasionally even though select reported the fd is ready. OK ray@ Mon Apr 27 19:41:10 2009 + It's called `msgcat', not `mesgcat' Sun Apr 19 13:12:28 2009 + Fix tagnext and tagprev; from Patrick Keshishian Sun Feb 8 17:15:08 2009 + Bump the POSIX reference in STANDARDS to IEEE Std 1003.1-2008, with a few updates to follow Sun Feb 1 21:57:21 2009 + Move variable declarations around to compile with GCC 2 OpenBSD 4.3 -> OpenBSD 4.4: Wed Jan 28 21:30:43 2009 + Remove undocumented support for "-e" in ex(1) + ex(1), vi(1), and view(1) have different synopses; each nex/nvi utility should manage the right set of options and return an appropriate usage when required Thu Sep 25 11:37:03 2008 + do not hard code the editor name in the message displayed by "-r" when there are no files to recover as this flag is used by ex(1) and view(1) too Fri Aug 29 13:07:13 2008 + Fix nvi's Cscope support in the case that someone provided a filename without a directory (e.g. :cscope add cscope.out) Found and fixed by Paul Irofti, with help from me; Thanks! OpenBSD 4.2 -> OpenBSD 4.3: Thu Jun 12 21:22:48 2008 + Remove superfluous "usage:" from v_estr() Fri Mar 28 17:58:20 2008 + Minor ansification from Gleydson Soares Sat Mar 8 18:11:42 2008 + Avoid infinite recursion on certain error conditions; from NetBSD; ok millert@ + Fix vs_columns() for the "set nu" case. Avoids segfaults for very long lines containing tabs; from Nathan Houghton; ok millert@ Tue Mar 4 18:55:44 2008 + Fix ifdef DEBUG code; ok krw@ deraadt@ OpenBSD 4.1 -> OpenBSD 4.2: Sat Nov 24 12:59:28 2007 + Some spelling fixes from Martynas Venckus Wed Oct 17 20:10:44 2007 + Remove "unused variable" warnings Fri Sep 14 14:29:20 2007 + Remove some warnings: unused variable `variable' `variable' might be used uninitialized in this function Tue Sep 11 15:47:17 2007 + Use strcspn to properly overwrite '\n' in fgets returned buffer Sun Sep 2 15:19:07 2007 + Use calloc() to avoid malloc(n * m) overflows; checked by djm@ canacar@ jsg@ Thu Jul 26 16:11:56 2007 + Add the correct file descriptor to rdfd when cycling through the list of scripting windows. Appears to be a cut and paste error. OK deraadt@ Thu May 31 19:19:00 2007 + Convert to new .Dd format Wed May 30 04:41:33 2007 + Use a consistent text for STANDARDS + Note which options are extensions to POSIX OpenBSD 4.0 -> OpenBSD 4.1: Mon May 14 12:32:29 2007 + Use sys/queue macros instead of accessing fields directly; No binary change. ok krw@ Tue Mar 27 18:24:06 2007 + Catch OOB access for tag searches matching lines ending with a '\'; patch from Patrick Keshishian with a twist by me. ok thib@ Tue Mar 20 03:56:12 2007 + Remove some bogus *p tests from charles@ longeau@ ok deraadt@ millert@ Thu Dec 21 21:38:17 2006 + Fix !command piping by Alexander Bluhm in PR#5325. Tested by quite a few on tech@ OpenBSD 3.9 -> OpenBSD 4.0: Mon Dec 11 20:50:54 2006 + RFC 3834 support: Auto-Submitted: auto-generated on lots of things; from Tamas TEVESZ; ok millert@ Fri Jul 7 12:05:10 2006 + Don't add space for line numbers twice Sun Jun 18 20:41:24 2006 + Fix memleak; From Coverity Scan, CID#3135. From simonb@ NetBSD Tue May 30 19:43:27 2006 + Avoid double fclose(), from coverity/NetBSD; ok otto@ OpenBSD 3.8 -> OpenBSD 3.9: Sun May 21 19:21:30 2006 + Backport fix from nvi 1.81.5: do not go into loop if :set number and :set leftright and the cursor moves to an empty line PR#3154; ok beck@ Fri Apr 28 19:48:15 2006 + Ensure NULL termination after read(); ok ray@ Sat Apr 22 03:09:15 2006 + Removes unused variables and rename variables shadowing external variables; no binary change Mon Mar 20 01:00:36 2006 + If we're in visual mode reading a command, check the termination value of v_tcmd() and bail if it's not TERM_OK as opposed to in a more specific case. This is based on the NetBSD ^C fix but after discussion with otto@. While it did not affect the specific crash it is more correct Wed Mar 15 23:43:27 2006 + Handle ^C correctly, morph it to escape key so the input is correctly finished for a potential replay; if not, simply bail out and notify that something wrong occurs. Callers will cope. Consistent with what vim and Solaris vi do. Fixes a crash described in NetBSD PR#11544, fixed by aymeric@ ok otto@ ray@ Sat Mar 11 07:04:53 2006 + Fixes the `optindx' might be used uninitialized in this function warning, fixes a spacing nit in a macro, and cleans up a very bad preprocessor abuse (``if LF_ISSET(OS_DEF)''!) + Silence 2 warnings + Silence another 39 warnings + Silence uninitialized variable warning + Make FLUSH macro more function-like, so there are no hidden surprises; no binary change + Initialize p to NULL to prevent gcc warning. Clarify a for statement Sat Mar 4 16:18:04 2006 + Fix "the the" Fri Feb 17 19:12:41 2006 + Fix use after free. Problem hunted down by wilfried@; ok fgsch@ millert@ OpenBSD 3.7 -> OpenBSD 3.8: Sun Jan 8 21:10:04 2006 + Remove unused NADD_USLONG macro, and remove unused sp argument from NADD_SLONG; no functional change + Fix one more uninitialized variable scenario; from Ray Lai + Make sure we can exit from a loop in v_key_init() regardless of the locale we're in; from Ray Lai + Appease GCC 3 and the C gods by fixing a couple of undefined statements; from Ray Lai + Explicit braces around macro fields and logical operations, gets rid of 148 warnings, no functional change OpenBSD 3.6 -> OpenBSD 3.7: Mon Oct 17 19:04:19 2005 + Use queue macros instead of directly accessing fields ok pat@ "put it in" deraadt@ Thu Apr 21 15:39:31 2005 + Spelling typo in comment; from ray Thu Apr 21 09:00:25 2005 + Avoid the "tcsetattr: Interrupted system call" fatal error when resizing using a window manager that continuously sends resize events; ok camield@ miod@ Thu Mar 10 18:03:45 2005 + -v description comes before -w + Also a sentence tweak Sun Jan 9 01:44:35 2005 + Tidy up FAST STARTUP + Better example + Better section reference Sat Jan 8 05:22:25 2005 + Fix for FreeBSD PR#12801 from Sven Verdoolaege (nvi maintainer) via FreeBSD (an infinite loop at certain case when options "comment" and "leftright" are used) + Move a line of code which was "obviously" misplaced. This fixes a core dump when auto-completing filenames and at least one of the file names is larger than the screen width + When an error occurs in v_txt(), leave input mode too. Otherwise, (among other things) db_get() thinks it can reuse the TEXT buffers when it's not true, leading to a crash because that TEXT buffer will be released just before it is actually used to create a new one. From NetBSD, fixes NetBSD PR#21797 + Move the license into the body of the man page; ok millert@ Fri Jan 7 15:04:02 2005 + Remove line in copyright declaration that conflicts with the LICENSE file. OK bostic@sleepycat.com OpenBSD 3.5 -> OpenBSD 3.6: Mon Nov 29 21:51:08 2004 + Lowercase for consistency + Spell precede correctly. 'looks fine' millert@ krw@ ok jmc@ OpenBSD 3.4 -> OpenBSD 3.5: Mon Oct 4 21:45:59 2004 + Refer to re_format.7 rather than egrep.1 for a description of EREs Fri Apr 9 12:12:44 2004 + ex is not a screen editor Fri Mar 19 08:14:52 2004 + Clarify -c Fri Feb 20 20:05:05 2004 + Add `ruler' to the list of helpful options; suggested by millert@ + Add section on helpful ex options; suggested by and ok millert@ Fri Feb 20 13:15:51 2004 + Cleanup of 6.2: Options, set, and editor startup files Mon Feb 9 21:16:06 2004 + Point people to ex tutorial + Install edit USD; this has been updated/reworded to work as an ex tutorial Fri Jan 30 23:39:22 2004 + Use paper.txt, rather than some arbitrary target + Some additional cleanup + Point people to 13.ex, and remove some unnecessary text from SEE ALSO Fri Jan 30 23:14:25 2004 + Install exref; includes updates to sync with current behaviour fixes, help, and ok millert@ Sun Jan 25 23:22:10 2004 + Install all the catalogs; as cvs forgot to check this file in when those were added; millert@ ok Sat Jan 24 12:32:55 2004 + Oops. no need for vitut comment + Install vi tutorial docs; these have been updated to reflect reality; help and ok millert@ + Document how file recovery works on OpenBSD; ok millert@ + Make vi reference card and vi tutorial easier to find + Use -compact for FILES Fri Jan 16 13:08:32 2004 + Point people to vi.ref now that it's installed (and get its name right) + Correct a path and Nm Thu Jan 15 11:17:04 2004 + Return documented lines option to original (default) value as pointed out by millert@, it's terminal dependent + Update vi.ref to reflect reality; help and ok millert@ Wed Jan 14 23:40:01 2004 + Comment out reference to index.so when we are building index.so itself. With changes by jmc@ Mon Jan 12 18:16:50 2004 + Install vi.ref in /usr/share/doc/usd (directories already exist for it). OK jmc@ + There is no typewriter font in nroff so just use the roman font instead. Fixes bizarre font problems formatting vi.ref via nroff OK jmc@ + Use a 5n margin on the right & left sides so the text version formats nicely. OK jmc@ + Pass groff the -U flag so that building the index works. OK jmc@ Wed Jan 7 12:46:48 2004 + Corrections to SET OPTIONS + s/environmental/environment/ + Corrections to the EX COMMANDS section Fri Jan 2 21:37:48 2004 + Some corrections/improvements to the VI COMMANDS section + use standard section ENVIRONMENT, rather than ENVIRONMENT VARIABLES; from deraadt@ Wed Dec 31 18:56:21 2003 + Fix -r description now that millert@ has fixed the code Wed Dec 31 18:18:22 2003 + Both POSIX and the man page says that "vi -r foo" is run where foo does not exist and has no recovery file that vi shall present an error and edit foo as a new file. This change makes the behavior match the documentation; previously it just spat out an error and quit. Problem found by jmc@ + mdoc vi(1); also better document current behaviour and add some missing commands OpenBSD 3.3 -> OpenBSD 3.4: Sat Nov 8 19:17:27 2003 + Typos from Jonathon Gray Tue Sep 2 22:44:06 2003 + Switch to dynamic fd_set and poll; patch entirely from millert@. ok deraadt@, dhartmei@ Fri Aug 1 16:47:25 2003 + When the -R option (read-only) is specified, there is no need to print a warning that the file is read-only, it's obviously what's expected... ok fgsch@ henning@ Mon Jul 21 16:21:12 2003 + Updated license from nvi-1.81.5 since we will be pulling in patches from it + Merge back some changes from skimo's tree, fixes endless recursions in vs_paint() for some option combinations. ok millert@ Fri Jul 18 23:11:43 2003 + Add missing includes ok tedu@ Wed Jul 9 20:01:31 2003 + Fix double free; patch by Eric Jackson Wed Jul 2 00:21:16 2003 + Bump randomness of mktemp to from 6 to 10 X's, as recommended by mktemp(3) OpenBSD 3.2 -> OpenBSD 3.3: Tue Jun 3 02:56:05 2003 + Remove the advertising clause in the UCB license which Berkeley rescinded 22 July 1999. Proofed by myself and Theo Fri Apr 25 23:44:08 2003 + Oops; Fix comment Thu Apr 17 02:22:56 2003 + Eliminate strcpy/sprintf. Reviewed by deraadt@ and millert@ Tue Apr 15 21:34:53 2003 + No, vi does not ignore SIGQUIT + Change to use snprintf, of course Mon Apr 7 21:13:52 2003 + Replace strcpy calls that got inlined by gcc Hans-Joerg.Hoexer@yerbouti.franken.de Mon Mar 10 03:53:32 2003 + Spelling fixes ok millert@ Sun Jan 12 18:15:16 2003 + Typos; jmc@prioris.mini.pw.edu.pl Sun Dec 15 13:28:22 2002 + More writable spelling; torh@ Sat Nov 23 12:48:18 2002 + Typo: Edieroption->Editieroption ok mickey@ OpenBSD 3.1 -> OpenBSD 3.2: Tue Nov 19 17:00:22 2002 + Update ru as it was 7bit stripped and add ua and pl; from FreeBSD, pointed out by glebius@rinet.ru in PR#2552 OpenBSD 3.0 -> OpenBSD 3.1: Wed Jun 12 06:07:15 2002 + A real pid_t cleanup Tue Feb 19 19:39:35 2002 + We live in an ANSI C world; Remove lots of gratuitous #ifdef __STDC__ cruft + Oops, fix a left out ';' Mon Feb 18 23:56:10 2002 + Format string fixes Sun Feb 17 19:42:18 2002 + Manual cleanup of remaining userland __P use (excluding packages maintained outside the tree) Sat Feb 16 21:27:05 2002 + Part one of userland __P removal. Done with a simple regexp with some minor hand editing to make comments line up correctly Another pass is forthcoming that handles the cases that could not be done automatically OpenBSD 2.9 -> OpenBSD 3.0: Thu Jan 31 11:10:39 2002 + Bug-fix picked up from NetBSD, and checked by pval: : date: 2001/10/20 10:04:50; author: aymeric : Fix a cut_line() caller not using the right value for (former) ENTIRE_LINE, : by defining the (newer) CUT_LINE_TO_EOL define in common/cut.h and using it : where due. : Bug reported on current-users by Masanori Kanaoka : diagnosed by Bang Jun-Young , : quick-fixed by Robert Elz Mon Nov 19 19:02:13 2001 + Kill more registers Tue Nov 6 23:31:08 2001 + Change a stat() to lstat() Mon Nov 5 22:43:49 2001 + Add more sanity checks of path data in the vi recovery file potential problems pointed out by lumpy@the.whole.net Wed Sep 19 02:43:19 2001 + Define ENTIRE_LINE to be -1 instead of 0 because we may want to copy 0 characters, and use ENTIRE_LINE instead of hard coding 0 in a few places. Fixes a bug when dw on an empty line would delete only the empty line, but copy the next line too + Fix a bug where ^@ wouldn't behave as expected when reading an ex command from vi. From NetBSD, ok millert@ Mon Sep 17 04:42:55 2001 + Make vi exit if it can't create a temp file. From NetBSD, ok millert@ Sat Sep 15 15:41:19 2001 + Fix obvious omissions Tue Sep 11 22:31:29 2001 + Locale ru_SU is obsolete, replace with ru_RU mickey@ ok Sat Aug 18 20:35:13 2001 + Range check snprintf() return value + Fix a pasto I made when adding snprintf() return val checks ages ago Fri Jul 20 18:48:03 2001 + Make this work, after espie changed other mk behaviours Mon Jul 9 07:02:08 2001 + Correct type on last arg to execl(); nordin@cse.ogi.edu OpenBSD 2.8 -> OpenBSD 2.9: Mon Jun 18 21:39:25 2001 + When creating temp files, use fchmod() to set the perms to be what we expect since the mode mkstemp() uses can be modified by the umask. This fixes a problem where vi would spin trying to create temp files, eating up inodes; reported by xyntrix@bitz.org Mon May 28 22:44:32 2001 + Behave correctly when displaying an empty screen line when the corresponding file line is not empty itself. Avoids core dumps in the ':set list' mode (at least). NetBSD PR#4113; millert@ ok + Print SYSERR instead of ERR when recdir does not exist. Makes the message more useful for the user; from NetBSD, millert@ ok Wed Jan 17 00:57:33 2001 + Don't dump core when a ``bad address'' error occurs and there is neither a file nor a command underlying it + Fix NetBSD PR#11543; the fix is from Aymeric Vincent OpenBSD 2.7 -> OpenBSD 2.8: Thu Jan 11 04:56:52 2001 + grep() returns a list of aliases to entries in the original list so modifying them directly results in a munged line in the resulting mail message that gets sent out. Similar to a patch from cazz@wezl.org; closes PR#1617 Fri Nov 17 05:46:48 2000 + OpenBSD already has queue.h and this one gets in the way since OpenBSD includes expect macros in sys/queue.h that the vi queue.h didn't have + Userland programs should not include sys/select.h Sun Oct 22 00:16:27 2000 + Fix noprint/print/octal options; from NetBSD. Reviewed by millert@ Thu Oct 12 09:38:19 2000 + When checking mmap return, check for MAP_FAILED, not -1 Fri Sep 15 07:13:43 2000 + Check return value for setenv(3) for failure, and deal appropriately OpenBSD 2.6 -> OpenBSD 2.7: Wed Aug 2 04:10:44 2000 + $HOME paranoia: never use getenv("HOME") w/o checking for NULL and non-zero Fri Apr 21 17:06:13 2000 + Remove the races so that this is safe to run anytime. We open /var/tmp/vi.recover to get an fd and user O_NOFOLLOW to following a symlink. Once we have a file handle we can use it to safely chdir to the right place and form then on do operations relative to ".". Also restrict to root Thu Apr 20 15:24:24 2000 + If recovery dir is not owned by root, chown it. If the mode is not 01777, fix that too. This is safe because the script is run before user processes start Thu Mar 9 21:24:02 2000 + Pull in fnctl modeul so we are sure to get O_* for sysopen() Sat Jan 22 22:57:35 2000 + Some minor doc updates that should have gotten committed ages ago OpenBSD 2.5 -> OpenBSD 2.6: Thu Jan 20 18:19:45 2000 + Use sysopen() when opening recover files. This is purely paranoia since we check that the filename matches '^recover' and hence the first character cannot play games with Perl's magic open() Fri Nov 26 22:49:08 1999 + Update README files etc. from nvi-1.79 so they have the correct info + Make port.h empty, since we there is nothing we lack + Include , not in files that use MIN/MAX macros + Add Perl API support since we have libperl (off by default) Mon Oct 11 20:07:19 1999 + Rewrite in Perl for safety and paranoia. It might have been possible to play tricks with file names that include spaces Sat Jul 10 10:09:48 1999 + Fix a SEGV after you HUP vi; dean@netbsd.org Sat Jun 5 01:21:16 1999 + Remove trailing white space + Remove arguments from .Os macros + Remove arguments from .Nm macros, where appropriate + Fix some more Dq/Sq/Ql insanity Sat May 29 03:50:24 1999 + MLINKS, not MLINK OpenBSD 2.4 -> OpenBSD 2.5: Mon May 24 22:43:35 1999 + Set the close-on-exec flag for newly opened files Wed Mar 10 21:25:25 1999 + Fix comma splices involving 'however' Sat Mar 6 20:27:39 1999 + Back out changes that should not have escaped my local tree + Add missing reference to infocmp Wed Mar 3 01:22:33 1999 + Better grammar for err msg OpenBSD 2.3 -> OpenBSD 2.4: Mon Feb 8 01:26:56 1999 + Don't call curses routines beep() or flash() if the screen has not been setup yet (as they will try to us SP which is NULL at this point) Fri Jul 24 00:43:40 1998 + Man pages Xrefs + -D_USE_OLD_CURSE_ for -locurses and no more -ltermlib/-ltermcap OpenBSD 2.2 -> OpenBSD 2.3: Tue Jun 23 22:40:25 1998 + Fix snprintf return value usage OpenBSD 2.1 -> OpenBSD 2.2: Sat Apr 25 05:45:30 1998 + Fix relative tags in vi; Frank Mayhar Wed Sep 24 21:31:56 1997 + No, use new curses so that the build process works. Reevaluate this later Tue Sep 23 07:12:42 1997 + Make building with ocurses/termcap and curses/termlib conditional on USE_OCURSES being defined, and define it for now This switches nvi back to use BSD curses Nvi 1.79 -> OpenBSD 2.1: Sun Aug 24 19:15:25 1997 + Check for >= UINT_MAX not > UINT_MAX + 64bit fix wrt strtoul(3); fix sent to Keith Nvi 1.78 -> Nvi 1.79: 10/23/1996 + Rename delete() to del(), for C++ + Add Spanish to the list of translations + Update to Perl 5.003_06, and other Perl interpreter updates + Update the set-edit-option interface for the scripting languages + Rework ex command parsing to match historic practice for backslash escaped characters inside of global commands + Enhance the comment edit option to skip C++ comments + Change installation to configure the recovery shell script to match the system pathnames and to install it into the vi data directory; move the recover script into the build directory, and delete the recover directory + Enhance LynxOS support Nvi 1.76 -> Nvi 1.78: 10/1/1996 + Fix bugs when both the leftright scrolling and number edit options were on + Fix bug where splitting in the middle of the screen could repaint incorrectly + Fix first-NULL in input bug, where random garbage was inserted + Correct search and mark-as-motion-command bug, it's a line mode action if the search starts at or before the first non + Fix bug autoindent bug, where ^D could shift too far in the line + Fix core dump where ! command called from the .exrc file + Add the -S command-line option, which initializes vi to have the secure edit option preset Nvi 1.75 -> Nvi 1.76: 9/15/1996 + Fix bug where ^V didn't keep input mapping from happening + Fix a core dump bug in the R command + Give up on licensing: no more shareware, adware, whatever + Fix cursor positioning bug for C, S and c$ in an empty file Nvi 1.74 -> Nvi 1.75: 8/22/1996 + Add French to the error message translations + Move the UNLICENSED message to the end of the message line + Fix bug where wide characters in a file name weren't calculated correctly in the status message + Fix bug where cl_rename was called directly, by the ex shell code + Fix bug where splitting a screen resulting in a new screen at the top of the display resulted in badly displayed status messages Nvi 1.73 -> Nvi 1.74: 8/18/1996 + Fix bug where the status line wasn't redisplayed if the user ran an ex command that trashed the screen + Fix bug where the long version of the status line wasn't displayed when switching screens + Rework fast-path filename completion code to sort the entries, and strip out . and .. by default + Fix bug where ex went to the first line instead of the last one when reading in a file Nvi 1.72 -> Nvi 1.73: 8/12/1996 + Do filename completion and some file expansion internally for speed + Fix CSCOPE_DIRS environmental variable support + Ex parser fix for global commands in script files + Add the O_PATH option, so you can specify a directory search path for files + Make it possible to specify the database file to Cscope, allowing multiple databases in a single directory + Fix incremental search to overwrite erased characters so the user can tell where they are on the colon-command line + Fix incremental search to restart the search if the user enters an un-escaped shell meta character Nvi 1.71 -> Nvi 1.72: 7/12/1996 + Cscope fix: test for files newer than the database was reversed + Display "files to edit" message for rewind, next and initial screen + Fix a bug in the R command where it could fail if the user extended the file + Fix a bug where text abbreviations could corrupt the line + Fix a bug where the windowname edit option couldn't be set before a file was loaded into the edit buffer + Fix a bug where the system .exrc values weren't being overridden by the user's $HOME .exrc values + Fix a bug in the filename completion code, where garbage characters could be added to the colon command line + Fix bug where multiple edit sessions on a non-existent file could all write the file without warning + Fix bug where screen update was incorrect if a character triggered both a wrapmargin and showmatch condition + Fix bug in leftright scrolling where during text input didn't return the cursor to the left margin + Rev the Perl interpreter code, new version from Sven Verdoolaege, based on Perl 5.003.01 + Fix bug in tags file pattern search introduced in 1.71 Nvi 1.70 -> Nvi 1.71: 7/1/1996 + Don't include as neither HP-UX or Solaris can cope with it + Fix bug where ^M's in the original pattern were converted into new lines in the file during substitution commands + Make window resize events separate from interrupts - too many users complained + Fix bug in first-character-is-NULL text input semantic + Rework search routines to take a length instead of a NULL terminated string for a pattern. This fixes a couple of bugs in searching, but probably introduces new ones + Fix prompting the user after a write filter command, the way I did it in 1.70 broke the display + Don't switch to the alternate xterm screen when entering the ex text input commands from vi mode + Implement the Fg command, so can foreground a background screen into a split screen + Change the fg command to match screen names using the last component of the filename the full filename fails Nvi 1.69 -> Nvi 1.70: 6/28/1996 + Change the ex read command to support named pipes + Copy the EXINIT/NEXINIT strings before executing their commands so we don't step on the process environment + Don't do "line modification" reports for intermediate commands executed from the vi colon command line, it screws up filter reads, causing nvi to prompt for the user to continue + Add "smd" as an abbreviation for showmode: HP, ICL and SCO have it + Change nvi to always prompt the user after a write filter command to match historic practice + Fix recovery information mailed to the user to reflect the program's installed name + Change configuration script to not cache option information, e.g., --disable-curses + Fix a bug where the second character of the vi [[, ]] and ZZ commands could start a command mapped sequence + Fix 3 write bugs: partial writes (3,$write), were clearing the modified flag, full writes using line numbers (1,$write) were not, and append historically never cleared the modified flag, and we didn't get that right + Shorten the "more files to edit" message so it can gang on a single line, lots of people have complained. Add the number of files that are left to edit, it's historic practice + Fix core dump where message catalogs collided with truncating the write path. Add a new write message so the string "appended" is taken from a message catalog + Fix bug where an undo followed by '.' to repeat it wouldn't work if no other repeatable commands had been entered + Fix core dump when resolution of input lines' autoindent characters invalidated cached display information + Set the name of the X11 xterm icon/window to "xterm" when exiting, if modified based on the windowname option + Include if it exists, fixes portability problems on IRIX systems Nvi 1.68 -> Nvi 1.69: 6/17/1996 + Add the windowname edit option and code to change the icon/window name for xterm's + Enhance the comment edit option to skip shell comments + Add conditional prototypes to replacement C library functions + Minor enhancements/reworking to Makefile.in, other build files + Fix bug in vi text input ^D processing, could result in cursor warp to the beginning of the line + Fix leftright screen bug where the screen wasn't repainted when being repainted from scratch + Update the Swedish and Dutch catalogs + Truncate paths in write commands if they don't fit on one line + Fix alternate screen bug where the screen flashed and output lost when switching to/from the X11 xterm alternate screen. Fix bug where nvi switched into the alternate screen during filter-read commands, which doesn't match historic practice + Minor relative cursor positioning change, make cursor position changes from ex real and permanent Nvi 1.67 -> Nvi 1.68: 6/9/1996 + Fix core dump when tagging out of a modified file Nvi 1.66 -> Nvi 1.67: 6/9/1996 + Convert the license to adware + Leftright scrolling tweak, don't repaint the screen as often + Change so that search warning/error messages don't appear during an incremental search + Cscope fix: test for files newer than the database was reversed + Don't display ex `welcome message' if in ex batch mode + Test for vsnprintf and snprintf separately, HP 10.10 has snprintf but not vsnprintf + Reverse lookup order between LC_MESSAGES and LANG + Fix Tcl/Perl core dumps in common API code to get/set options + Fix R command - it used a DB pinned page after discarding it + Minor fixes in multiple edit buffer message handling code + Fix yk command moving to shorter line core dump + Rework message handling to try and gang more messages onto a single line Nvi 1.65 -> Nvi 1.66: 5/18/1996 + Convert vi man page to historic -man macro package, and install it + Fix bug were !! on an empty line with a nonexistent command left the cursor on the second character, not the first + Fix bug where line redisplay was wrong when a replaced a previous in the line + Fix bug where D (d$) didn't reset the relative cursor position + Fix bug where yG incorrectly reset the relative cursor position + Fix bug where the window size couldn't be grown once it was shrunk + Fix bug where the extended edit option caused tag searches to fail + If multiple lines in the tags file with the same leading tag, build a tags stack like the Cscope stack. This is the obvious extension, and the way that Larry McVoy's ctags program works + Send the appropriate TI/TE sequence in the curses screen whenever entering ex/vi mode. This means that :shell now shows the correct screen when using xterm alternate screens + Rework the options display code to get five columns in an 80 column screen + Interactive UNIX V3.0 port - mostly file name shortening, other minor changes. Only preliminary, more work will be necessary + Add debugging option to not read EXINIT/.exrc information + Fix bug where re_compile printed an error message to the screen when the user entered [ to an incremental search + Turn off screen beeps when incremental search is failing + Fix bug where the iclower option didn't trigger an RE recompilation + Fix bug where -t into an already locked file forced the user to wait as if a startup command had failed + LynxOS port - mostly adding even though was already included + Fix ex output bug, where it appeared as if an ex command was skipped due to flags not being cleared in the vs_msg() routine + Fix core dump when global command tried to switch screens Nvi 1.64 -> Nvi 1.65: 5/13/1996 + Fix Cscope -matching pattern to use extended RE's, and bug that kept Cscope from finding patterns containing s + Fix core dumps in both leftright and folded screens when tabstops edit option value was large, and tab characters occurred as the last character in the logical screen + Fix core dump where the second screen of a folded line wasn't displayed correctly + Fix incremental search to match the current location for strings starting with \< patterns + Fix bug where margins were ignored during replay of text input + Fix bug where motion components to shorter lines could lose because the relative motion flags weren't ever set. This has been broken forever, but the change almost certainly breaks something else - I have no idea what + Tags display: don't print the current entry separately, display them all and add a trailing asterisk for the current one + Change the Cscope add command to put the directory name through standard file name expansion + Fix Cscope use of buffers - search commands weren't NULL- terminated Nvi 1.63 -> Nvi 1.64: 5/8/1996 + Add installation target to the Makefile + Add documentation on the new tags commands to the Vi Reference Manual + Make the sidescroll edit option work again + Fix bug where messages output during startup by ex could be lost + Change ex/vi commands errors into beeps, unless the verbose edit option is set - there are too many macros that are expected to eventually fail. This matches historic practice + Truncate paths in initial vi screen if they won't fit on one line + Make cursor position after filter write match historic practice + Force the user to wait if there is output and the user is leaving the screen for any reason - don't permit further ex commands + Don't use a character to scroll the screen when exiting, scroll in the vi screen before endwin() is called + Fix bug where the column number could be incorrect because the old screen wasn't updated after a screen split + Fix ex print routine to correctly specify print flags + Make -g/-O a separate make/configuration option + Fix bug where ex/vi messages weren't being joined + Fix bug where termcap strings were free'd twice + Fix bug where TI/TE still weren't working - I didn't put in the translation strings for BSD style curses + Fix bug where I misspelled the iclower edit option as icloser Nvi 1.62 -> Nvi 1.63: 4/29/1996 + Robustness and type/lint fixes for the Tcl interface code + Fix core dump if TERM wasn't set or terminal type was unknown + Fix bug where combining ex commands that did/did not require an ex screen would overwrite the command with the want-to-continue message + Fix bug where the screen was never resolved if the user continued entering ex commands using the : character, but then backspaced over the prompt to quit or tried to edit their colon command-line history + Fix bug where cursor wasn't placed over the ^ placeholder character when quoting using the literal-next character + Fix bug where nvi under BSD style curses wasn't sending TI/TE termcap strings when suspending the process + Rename mic again, to iclower + Fix bug where 'z' commands trailing / or ? commands weren't being executed + Change incremental search to leave the cursor at its last position when searching for something that was never found + Fix bug where search-with-confirmation from vi mode didn't position the cursor correctly after displaying the confirm message + Fix bug where the "search wrapped" message was dependent on the verbose edit option, which doesn't match historic practice. Change search messages to be in inverse video + Fix bug where matched showmatch character wasn't being displayed before the matching character was displayed + Another cursor update bug required a change to vs_paint() + Fix bug were initial line offset was wrong for the first split screen (symptom is very strange column numbers and blank first line) + Create filename "argument" lists when creating new screens + Fix bug where globals with associated commands that included both buffer execution and other commands could fail to execute the latter Nvi 1.61 -> Nvi 1.62: 4/22/1996 + Rename the "searchci" edit option to be "mic" + Fix memory corruption in global commands ending in searches + Fix text resolution bug, corrected the cursor based on the first line input, not the last + Rework the readonly edit option to match historic practice + Fix several minor incremental search bugs; make incremental searches work in maps + Fix long-line core dump, where an incorrect screen map could be used Nvi 1.60 -> Nvi 1.61: 4/12/1996 + The cursor now ends up on the FIRST character of the put text for all versions of the vi put commands, regardless of the source of the text. This matches System III/V behavior and POSIX 1003.2 + Fixed bug where showmatch messages were getting discarded + Minor Perl integration fixes + Integrate Cscope into the tags stack code - major change + Fixed bug where ^T would drop core if returning to a temporary file + Changed vs_ routine to display ex output to replace tab characters with spaces + Fix autoindent code to not back up past beginning of line when ^T inserted into the middle of a line, i.e. offset != 0 + Fix "notimeout" option, was being ignored, by a coding error + Fix showmatch code to never flash on a match if keys are waiting + Change the vi 'D' command to ignore any supplied count, matching historic practice + Fix viusage for D, S, C and Y (the aliased vi commands) + Fix the Perl5 configuration bug in the configuration script + Make file completion commands in empty lines work + Fix where the change to let vi use the default ex command structure broke the ex specification of the script or source file name + Fix to free saved RE structures when screens exit. This is a major RE change, which fixed several bugs in the handling of saved/subst RE's. It's likely to have added new bugs, however + Add case-independent searching (the searchci edit option) + Add incremental search (the searchincr edit option) + Home the cursor when executing ex commands from vi Nvi 1.59 -> Nvi 1.60: 3/29/1996 + Fix ":w >>" core dump, make that command match historic practice + Fix autoindent bug where the length of the line was incorrectly calculated + Fix cursor bug where cursor could end up at the wrong place if the movement keys were entered quickly enough + Change the read/write whirling indicator to appear only every 1/4 second, clean up the appearance + Don't change the options real values until underlying functions have returned OK - fix "set tabstop=0" core dump + Fix resizing on Sun's: use SA_INTERRUPT to interrupt read calls + Fix two forward mark command bugs: one where it wasn't setting the "favorite cursor" position because of the refresh optimization, and one where it didn't have VM_RCM_SET set in the command flags for some reason + Fix a bug were the 's' command on top of a didn't correctly copy the buffer + Make :exusage command work for commands having optional leading capital letters, e.g. Next + Previous changes broke the inital-matching-prefix code in the key mapping part of v_event_get - fix it, and fix the infinite macro interrupt code at the same time + Add "cedit" edit option, so colon command-line editing is optional Change filec/cedit so that you can set them to the same character, and they do cedit if in column 1, and filec otherwise + Fix "source of non-existent file" core dump + Fix bug where functions keys specified in startup information were never resolved/activated + Fix v_txt bug where could infinitely loop if triggered an abbreviation expansion + Move version string into VERSION file, out of ex_version.c Nvi 1.58 -> Nvi 1.59 + Configuration changes, several minor bug fixes, including a few core dumps. No functional changes Nvi 1.57 -> Nvi 1.58 + Fix the problem where colon command-line temporary files were getting left in /tmp + Fix the configuration scripts to quit immediately if the Perl or Tk/Tcl libraries are specified but not found + Several screen fixes - the changes in 1.57 weren't as safe as I thought. More specifically, the refresh-only-if-waiting change caused a lot of problems. In general, fixing them should provide even more speedup, but I'm nervous + Lots of changes in the configuration scripts, hopefully this is just a first-round ordeal + Several other minor bug fixes Nvi 1.56 -> Nvi 1.57 + Add hook to colon commands, so you can edit colon commands + Add Perl5 interpreter + Change shell expansion code to fail if it doesn't read at least one non-blank character from the shell. If the shell expansion process fails, or if not at least one non-blank character, it now displays an error message to the user + Rework the screen display so that it matches the historic vi screen refreshes + Rework options processing: print/noprint are no longer cumulative, provide more information to underlying edit options modules, move O_MESG information into the screen specific code + Make file completion character settable + Rework terminal restart - you can now use ":set term" to switch terminal types. This cleaned up screen resizing considerably + Character display fix, display \177 as ^?, not in hex/octal + Tag search bug fix, don't repeat search if successful + Replace sys_siglist[] use with private sigmsg() routine + Fix core dump if illegal screenId specified to Tcl routine + Add get/set mark interface to Tcl Interpreter interface + Fix core dump if file expansion code stressed (re: filec edit option) + Fix bug where filter commands in empty files couldn't find line 0 + Switch to GNU autoconf 2.7 for configuration, delete nvi/PORT + Many random portability fixes Nvi 1.55 -> Nvi 1.56: 11/26/1995 + Bug fix release - generally available beta release Nvi 1.54 -> Nvi 1.55: 11/18/1995 + Bug fix release + Integrate Tcl interpreter Nvi 1.53 -> Nvi 1.54: 11/11/1995 + Bug fix release. A major change in reworking the ex commands, when called from the colon command line, to match historic practice, and permit them to be entered repeatedly after ex has trashed the screen + Use restart-able endwin() from System V curses to implement screen suspend Nvi 1.52 -> Nvi 1.53: 10/29/1995 + Switch to using vendor's curses library for all ports + Back out the event driven version, leaving screen separation + User configuration of timeout (the escapetime edit option) + Add Tcl/Tk screen support + Add file name completion (the filec edit option) + Disallow access to outside applications (the secure edit option) Nvi 1.51 -> Nvi 1.52: 7/26/1995 + Minor cleanups + Snapshot for SMI Nvi 1.50 -> Nvi 1.51: 7/5/1995 + Lots and lots of changes for event driven model, largely in moving the boundary between the screen code and the editor up and down Private release for Rob Zimmermann @ Tartan and Bill Shannon @ SMI Nvi 1.49 -> Nvi 1.50: Fri Jun 9 13:56:17 1995 + Minor bug fixes for stability + Convert to an event driven model, with the usual Nachos Supreme layering that results. This is a completely new version, nothing done previously matters any more Nvi 1.48 -> Nvi 1.49: Wed Mar 8 10:42:17 1995 + Changes in 1.46 broke ^A processing + Add :previous to split screen commands + Lots o' random bug fixes - passes purify testing again Nvi 1.47 -> Nvi 1.48: Thu Feb 9 18:13:29 1995 + Random bug fixes for 1.47 + Move the FREF (file structure) list out of the screen and into the global area + Change semantics to :E to more closely match :e - ":E" joins the current file, so ":E /tmp" is now the command to match the historic ":split" Nvi 1.46 -> Nvi 1.47: Wed Feb 8 19:43:41 1995 + All ex commands (including visual and excluding global and v) are now supported inside ex global commands + Rework the append/change/insert commands to match historic practice for text appended to the ex command line, and inside of ex global commands + Restructure to make single-line screens work + Restructure to create curses independent screen routines + Restructure to permit Edit, Next, and Tag routines to create new screens on the fly + Change hexadecimal output to be \x## instead of 0x## + Change ex commands run from vi to stay in vi mode for as long as possible, i.e. until ex modifies the screen outside of the editor Nvi 1.45 -> Nvi 1.46: Tue Jan 24 10:22:27 1995 + Restructure to build as a library Nvi 1.44 -> Nvi 1.45: Thu Jan 12 21:33:06 1995 + Fix relative cursor motion to handle folded lines + Recompile the search pattern if applicable edit options change + Change +/-c command ordering to match historic practice + Rework autoindent code to always resolve preceding characters when a ^T or ^D are entered + Add the print/noprint edit options, so can now specify if a character is printable + Change ex to run in canonical mode + Fix ex text input to support the number edit option + Vi text input fix for the R command to correctly restore characters entered and then backspaced over + Several vi increment command fixes Nvi 1.43 -> Nvi 1.44 + Bug fix, vi was printing the last line number on the status line at startup. Change to execute commands at first line set, i.e. "vi -t tag -c cmd" executes cmd at the tag line, not EOF Nvi 1.42 -> Nvi 1.43: Sat Dec 3 13:11:32 1994 + Marks, SunOS signed comparison fix for 1.42 Nvi 1.41 -> Nvi 1.42: Fri Dec 2 20:08:16 1994 + Make autowrite require the file not be read-only + Make the ex insert command work in empty files + Tab expansion is no longer limited to values < 20 (which matches historical practice) + Simplify (and fix limit detection for) the # command. It's no longer possible to use the # command itself to repeat or modify a previous # command, '.' is the only possibility + Lots more reworking of the ex addresses, putting ? and / into the ex addressing code broke the world + Make the Put, Preserve and Print commands work (don't ask) + Split stdout/stderr from shell expansions; stdout is expansion text, stderr is entered on the message queue Nvi 1.40 -> Nvi 1.41: Fri Nov 18 16:13:52 1994 + Addition of a port for AUX 3.1 + Addition of a message catalog for Russian + Make vi ? and / commands be true ex addresses (historic practice) + Display the date first in vi -r recovery list Nvi 1.39 -> Nvi 1.40: Mon Nov 14 10:46:56 1994 + Two bug fixes for 1.39; -r option and v_change core dump Nvi 1.38 -> Nvi 1.39: Sun Nov 13 18:04:08 1994 + Ex substitution with confirmation now matches historic practice (except that it still runs in raw mode, not cooked) + Nvi now clears the screen before painting, if repainting the entire screen + Fix final cursor position for put command entering text in a single line + Change to break error message lines on the last in the line + Always center the current line when returning to a previously edited file or moving to a tag line that's not visible on the screen + Change write of the current file using an explicit name or % to match the semantics of :w, not :w file + Add command aliases to vi, and remap 6 historic commands to their historic counterparts: D->d$, Y->y_, S->c_, C->c$, A->$a, I->^i + Match option display to historic practice; if boolean or numeric options changed to default values, not displayed by default Nvi treats string options the same way, vi always displayed any string option that was changed + Added lock edit option, if not set, no file locking is done + Rework ex to permit any ex command in the EXINIT variable or exrc startup files. This fixes the bug were `vi +100 file' painted the screen and then moved to line 100 and repainted (Yanked to SCCS ID 9.1.) + Bug fix: could report file modified more recently than it was written, incorrectly + Search fix: historically, motions with deltas were not corrected to the previous/next line based on the starting/stopping column + Addressing fixes: make trailing non-existent addresses work, change % to be text substitution, not a unique address (to follow future POSIX) Nvi 1.37 -> Nvi 1.38: Mon Oct 24 12:51:58 1994 + Scrolling fix; ^B can move to nonexistent lines + Fix to vi mapped commands; characters while already in command mode did not historically cause the mapped characters to be flushed + Add the backup edit option, automatically version edit files + Make it possible to edit files that db can't read, i.e. edit a temporary file, with the correct file name + Only anchor the last line of the file to the bottom line of the screen if there's half or less of a screen between the target line and the end of the file + Fix wrapmargin text allocation bug + Fix ex put command to work in any empty file + Fix global command to handle move's to line 0 correctly + Regularize the yank cursor motions, several bug fixes for historic practice + Fix N and n, when used as a motion command for the ! command, repeat the last bang command instead of prompting for a new one + Timeout maps beginning with quickly, instead of based on the keytime option + Bug fix for wraplen option, wasn't triggered for input commands Nvi 1.36 -> Nvi 1.37: Sun Oct 9 19:02:53 1994 + Change PORT directories to install patches before distribution + Fix ^A to set search direction and pattern for consistency + Fold the showdirty option into the showmode option + Ex addressing fix: change search offset and line arguments (e.g. the copy command) to be ex addressing offsets, matching historic practice + Ex addressing fix: support ^ as an offset/flag equivalent to - + Ex addressing fix: historically, any missing address defaulted to dot, e.g. "4,,," was the same as ".,." + Ex addressing fix: historically, separated numbers were additive, e.g. "3 5p" displayed line 8 + Ex addressing fix: make ';' as a range delimiter match historic practice + Change nvi to exit immediately if stdout isn't a terminal + Change alternate file name behavior to match historic practice, make the :write command set the current file name + Text input fix; input keys from a map, with an associated count, weren't historically affected by the wrapmargin value + Add wraplen option, same as wrapmargin, but from the left-hand column, not the right + Make ex address . be equivalent to .+, i.e. the '+' is understood; matches historic practice, and it's widely documented for ed(1) + Input mode ^V^J historically mapped into a single ^J + Minor catalog changes, fixes; don't use 's' to pluralize words Nvi 1.35 -> Nvi 1.36: Thu Sep 8 08:40:25 1994 + Don't overwrite user's maps with standard (termcap) mappings + Make \ escape kill and erase characters in vi text input mode + Fix ^D autoindent bug by resolving leading s at ^D + Rework abbreviation tests (again!) to match historic practice + Change ^D/^U default scrolling value to be based on window option value, not screen lines, correct scrolling option value, both to match historic practice. NOTE: System V does this differently! Nvi 1.34 -> Nvi 1.35: Wed Aug 31 19:20:15 1994 + Add the historic -l option + Message catalogs + Display global messages at each flush, just in case some are there + Fix global substitute code, `\\' wasn't handled correctly + Fix abbreviation code to use s as the preceding character + Fix ruler to display logical column, not physical column + Block signals when user issues :preserve command, so no race caused by SIGHUP/SIGTERM Nvi 1.33 -> Nvi 1.34: Wed Aug 17 14:37:32 1994 + Back out sccsid string fix, it won't work on SunOS 4.1 Nvi 1.32 -> Nvi 1.33: Wed Aug 17 09:31:41 1994 + Get back 5K of data space for the sccsid strings + Fix bug where cG fix in version 1.31 broke cw cursor positioning when the change command extended the line + Fix core dump in map/seq code if character larger than 7 bits + Block signals when manipulating the SCR chains + Fix memory allocation for machines with multiple pointer sizes Nvi 1.31 -> Nvi 1.32: Mon Aug 15 14:27:49 1994 + Turn off recno mmap call for Solaris 2.4/SunOS 5.4 Nvi 1.30 -> Nvi 1.31: Sun Aug 14 13:13:35 1994 + Fix bug were cG on the last line of a file wasn't done in line mode, and where the cursor wasn't positioned correctly after exiting text insert mode + Add termcap workaround to make function keys greater than 9 work correctly (or fail if old-style termcap support) + Change ex/vi to not flush mapped keys on error - this is historic practice, and people depended on it + Rework vi parser so that no command including a mapped key ever becomes the '.' command, matching historic practice + Make cancellation in the vi parser match POSIX 1003.2 + Fix curses bug where standout string was written for each standout character, and where standout mode was never exited explicitly Fix bugs in curses SF/sf and SR/sr scrolling, as seen on Sun and x86 consoles + The v/global commands execute the print command by default + The number option historically applies to ex as well as vi Nvi 1.29 -> Nvi 1.30: Mon Aug 8 10:30:42 1994 + Make first read into a temporary set the file's name + Permit any key to continue scrolling or ex commands - this allows stacked colon commands, and matches historic practice + Don't output normal ! command commentary in ex silent mode + Allow +/- flags after substitute commands, make line (flag) offsets from vi mode match historic practice + Return to ex immediately, even if preceded by spaces. Rework ex parser to do erase the prompt instead of depending on the print routines to do it. Minor fixes to the ex parser for display of default and scrolling commands. MORE EX PARSER CHANGES Nvi 1.28 -> Nvi 1.29: Fri Aug 5 10:18:07 1994 + Make the abbreviated ex delete command work (:dele---###lll for example, is historically legal + When autoprint fires, multiple flags may be set, use ex_print directly instead of the stub routines + Change v/global commands to turn off autoprint while running + Minor changes to make the ! command display match historic output + Rework the ex parser to permit multiple command separators without commands - MAJOR CHANGE, likely to introduce all sorts of new bugs + Fix cd command to expand argument in the context of each element of the cdpath option, make relative paths always relative to the current directory + Rework write/quit cases for temporary files, so that user's don't discard them accidentally + Check for window size changes when continuing after a suspend + Fix memory problem in svi_screen, used free'd memory + Change the ex change, insert, append commands to match historic cursor positions if no data entered by the user + Change ex format flags (#, l, p) to affect future commands, not just the current one, to match historic practice + Make the user's EOF character an additional scroll character in ex + Fix ex ^D scrolling to be the value of the scroll option, not half the screen + Fix buffer execution to match historic practice - bugs where the '*' command didn't work, and @ didn't work + Fix doubled reporting of deleted lines in filters + Rework the % ` / ? ( ) N n { and ^A commands to always cut into numeric buffers regardless of the location or length of the cut This matches historic practice + Fix the { command to check the current line if the cursor doesn't start on the first character of the line + Do '!' expansion in the ex read command arguments, it's historic practice. In addition, it sets the last '!' command Nvi 1.27 -> Nvi 1.28: Wed Jul 27 21:29:18 1994 + Add support for scrolling using the CS and SF/sf/SR/sr termcap strings to the 4BSD curses + Rework of getkey() introduced a bug where command interrupt put nvi into an infinite loop + Piping through a filter historically cut the replaced lines into the default buffer, although not the numeric ones + Read of a filter and !! historically moved to the first nonblank of the resulting cursor line (most of the time) + Rework cursor motion flags, to support '!' as a motion command Nvi 1.26 -> Nvi 1.27: Tue Jul 26 10:27:58 1994 + Add the meta option, to specify characters the shell will expand + Fix the read command to match historic practice, the white space and bang characters weren't getting parsed correctly + Change SIGALRM handler to save and restore errno + Change SunOS include/compat.h to include so that the ex/filter.c code works again + Don't put lines deleted by the ex delete command into the numeric buffers, matching historic practice + Fix; if appending to a buffer, default buffer historically only references the appended text, not the resulting text + Support multiple, semi-colon separated search strings, and 'z' commands after search strings + Make previous context mark setting match historic practice (see docs/internals/context) + Fix the set command to permit whitespace between the option and the question mark, fix question marks in general + Fix bug where ex error messages could be accidentally preceded by a single space + Fix bug where curses reorganization could lose screen specific mappings as soon as any screen exited + Fix bug in paragraph code where invalid macros could be matched + Make paragraph motions stop at formfeed (^L) characters + Change 'c' to match historic practice, it cut text into numeric buffers Nvi 1.25 -> Nvi 1.26: Tue Jul 19 17:46:24 1994 + Ignore SIGWINCH if the screen size is unchanged; SunOS systems deliver one when a screen is uncovered + Fix: don't permit a command with a motion component to wrap due to wrapscan and return to the original cursor position + Fix: ^E wasn't beeping when reaching the bottom of the file + Fix bg/fg bug where tmp file exiting caused a NULL dereference + Rework file locking code to use fcntl(2) explicitly + Fix bug in section code where invalid macros could be matched + Fix bug where line number reset by vi's Q command + Add explicit character mode designation to character mode buffers + Add include to sex/sex_window.c, needed by NET/2 vintage systems + Change to always flush a character during suspend, 4BSD curses has the optimization where it doesn't flush after a standend() + Fix bug on OSF1 where changes the values of VERASE, VKILL and VWERASE to incorrect ones + Fix bug where optarg used incorrectly in main.c + Block all signals when acting on a signal delivery + Fix recovery bug where RCV_EMAIL could fire even if there wasn't a backing file; format recovery message Nvi 1.24 -> Nvi 1.25: Sun Jul 17 14:33:38 1994 + Stop allowing keyboard suspends (^Z) in insert mode, it's hard to get autowrite correct, and it's not historic practice + Fix z^, z+ to match historic practice + Bug in message handling, "vi +35 non-existent_file" lost the status message because the "+35" pushed onto the stack erased it. For now, change so that messages aren't displayed if there are keys waiting - may need to add a "don't-erase" bit to the character in the stack instead + Bug in svi_msgflush(), where error messages could come out in normal video Nvi 1.23 -> Nvi 1.24: Sat Jul 16 18:30:18 1994 + Fix core dump in exf.c, where editing a non-existent file and exiting could cause already free'd memory to be free'd + Clean up numerous memory errors, courtesy of Purify + Change process wait code to fail if wait fails, and not attempt to interpret the wait return information + Open recovery and DB files for writing as well as reading, System V (fcntl) won't let you acquire LOCK_EX locks otherwise + Fix substitute bug where could malloc 0 bytes (AIX breaks) + Permit the mapping of , it's historic practice + Historic vi didn't eat characters before the force flag, match historic practice + Bug in ex argument parsing, corrected for literal characters twice + Delete screen specific maps when the screen closes + Move to the first non- in the line on startup; historic practice + Change the ex visual command to move directly to a line if no trailing 'z' command + Fix "[[" and "]]" to match historic practice (yet again...) + Fix "yb" and "y{" commands to update the cursor correctly + Change "~" to match the yank cursor movement semantics exactly + Move all of the curses related code into sex/svi - major rework, but should help in future ports + Fix bug in split code caused by new file naming code, where would drop core when a split screen exited + Change svi_ex_write to do character display translation, so that messages with file names in them are displayed correctly + Display the file name on split screens instead of a divider line + Fix move bug, wasn't copying lines before putting them + Fix bug were :n dropped core if no arguments supplied + Don't quote characters in executed buffer: "ifoo" should leave insert mode after the buffer is executed + Tagpop and tagpush should set the absolute mark in case only moving within a file + Skip leading whitespace characters before tags and cursor word searches + Fix bug in ex_global where re_conv() was allocating the temporary buffer and not freeing it Nvi 1.22 -> Nvi 1.23: Wed Jun 29 19:22:33 1994 + New required "inline" to change to "__inline" + Fix System V curses code for new ^Z support + Fix off-by-one in the move code, avoid ":1,$mo$" with only one line in the buffer + Line orientation of motion commands was remembered too long, i.e. '.' command could be incorrectly marked as line oriented + Move file modification time into EXF, so it's shared across split screens + Put the prev[ious] command back in, people complained + Random fixes to next/prev semantics changed in 1.22 + Historically vi doesn't only move to the last address if there's ANYTHING after the addresses, e.g. ":3" moves to line 3, ":3|" prints line 3 Nvi 1.21 -> Nvi 1.22: Mon Jun 27 11:01:41 1994 + Make the line between split screens inverse video again + Delete the prev[ious] command, it's not useful enough to keep + Rework :args/file name handling from scratch - MAJOR CHANGE, likely to introduce all sorts of new bugs + Fix RE bug where no sub-expressions in the pattern but there were sub-expressions referenced in the replacement, e.g. "s/XXX/\1/g" + Change recovery to not leave unmodified files around after a crash, by using the owner 'x' bit on unmodified backup files MAJOR CHANGE, the system recovery script has to change! + Change -r option to delete recovery.* files that reference non- existent vi.* files + Rework recovery locking so that fcntl(2) locking will work + Fix append (upper-case) buffers, broken by cut fixes + Fix | to not set the absolute motion mark + Read $HOME/.exrc file on startup if the effective user ID is root. This makes running vi while su(1)'d work correctly + Use the full pathname of the file as the recovery name, not just the last component. Matches historic practice + Keep marks in empty files from being destroyed + Block all caught signals before calling the DB routines + Make the line change report match historic practice (yanked lines were different than everything else) + Add section on multiple screens to the reference manual + Display all messages at once, combine onto a single line if possible. Delete the trailing period from all messages Nvi: 1.20 -> Nvi 1.21: Thu May 19 12:21:58 1994 + Delete the -l flag from the recover mail + Send the user email if ex command :preserve executed, this matches historic practice. Lots of changes to the preserve and recovery code, change preserve to snapshot files (again, historic practice) + Make buffers match historic practice: "add logically stores text into buffer a, buffer 1, and the unnamed buffer + Print characters as ^I on the colon command line if the list option set + Adjust ^F and ^B scroll values in the presence of split screens and small windows + Break msg* routines out from util.c into msg.c, start thinking about message catalogs + Add tildeop set option, based on STEVIE's option of the same name Changes the ~ command into "[count] ~ motion", i.e. ~ takes a trailing motion + Chose NOT to match historic practice on cursor positioning after consecutive undo commands on a single line; see vi/v_undo.c for the comment + Add a one line cache so that multiple changes to the same line are only counted once (e.g. "dl35p" changes one line, not 35) + Rework signals some more. Block file sync signals in vi routines that interface to DB, so can sync the files at interrupt time Write up all of the signal handling arguments, see signal.c Nvi: 1.19 -> Nvi 1.20: Thu May 5 19:24:57 1994 + Return ^Z to synchronous handling. See the discussion in signal.c and svi_screen.c:svi_curses_init() + Fix bug where line change report was wrong in util.c:msg_rpt() Nvi: 1.18 -> Nvi 1.19: Thu May 5 12:59:51 1994 + Block DSUSP so that ^Y isn't delivered at SIGTSTP + Fix bug - put into an empty file leaves the cursor at 1,0, not the first nonblank + Fix bug were number of lines reported for the 'P' command was off-by-one + Fix bug were 0^D wasn't being handled correctly + Delete remnants of ^Z as a raw character + Fix bug where if a map was an entire colon command, it may never have been displayed + Final cursor position fixes for the vi T and t commands + The ex :next command took an optional ex command as it's first argument similar to the :edit commands to match historic practice Nvi 1.17 -> Nvi 1.18: Wed May 4 13:57:10 1994 + Rework curses information in the PORT/Makefile's + Minor fixes to ^Z asynchronous code Nvi 1.16 -> Nvi 1.17: Wed May 4 11:15:56 1994 + Make ex comment handling match historic practice + Make ^Z work asynchronously, we can no longer use the SIGTSTP handler in the curses library Nvi 1.15 -> Nvi 1.16: Mon May 2 19:42:07 1994 + Make the 'p' and 'P' commands support counts, i.e. "Y10p" works + Make characters that map to themselves as the first part of the mapping work, it's historic practice + Fix bug where "s/./\& /" discarded the space in the replacement string + Add support for up/down cursor arrows in text input mode, rework left/right support to match industry practice + Fix bug were enough character remapping could corrupt memory + Delete O_REMAPMAX in favor of setting interrupts after N mapped characters without a read, delete the map counter per character MAJOR CHANGE. All of the interrupt signal handling has been reworked so that interrupts are always turned on instead of being turned on periodically, when an interruptible operation is pending + Fix bug where vi wait() was interrupted by the recovery alarm + Make +cmd's and initial commands execute with the current line set to the last line of the file. This is historic practice + Change "lock failed" error message to a file status message It always fails over NFS, and making all NFS files read-only isn't going to fly + Use the historic line number format, but check for overflow + Fix bug where vi command parser ignored buffers specified as part of the motion command + Make [@*]buffer commands on character mode buffers match historic practice + Fix bug where the cmap/chf entries of the tty structure weren't being cleared when new characters were read + Fix bug where the default command motion flags were being set when the command was a motion component + Fix wrapmargin bug; if appending characters, and wrapmargin breaks the line, an additional space is eaten Nvi 1.14 -> Nvi 1.15: Fri Apr 29 07:44:57 1994 + Make the ex delete command work in any empty file + Fix bug where 't' command placed the cursor on the character instead of to its left + ^D and ^U didn't set the scroll option value historically Note, this change means that any user set value (e.g. 15^D) will be lost when splitting the screen, since the split code now resets the scroll value regardless + Fix the ( command to set the absolute movement mark + Only use TIOCGWINSZ for window information if SIGWINCH signal caught + Delete the -l flag, and make -r work for multiple arguments + Add the ex "recover[!] file" command + Switch into ex terminal mode and use the sex routines when append/change/insert called from vi mode + Make ^F and ^B match historic practice. This required a fairly extensive rework of the svi scrolling code + Cursor positioning in H, M, L, G (first non-blank for 1G) wasn't being done correctly. Delete the SETLFNB flag. H, M, and L stay logical movements (SETNNB) and G always moves to the first non- blank + System V uses "lines" and "cols", not "li" and "co", change as necessary. Check termcap function returns for errors + Fix ` command to do start/end of line correction, and to set line mode if starting and stopping at column 0 + Fix bug in delete code where dropped core if deleted in character mode to an empty line. (Rework the delete code for efficiency.) + Give up on SunOS 4.1.X, and use "cc" instead of /usr/5bin/cc + Protect ex_getline routine from interrupted system calls (if possible, set SA_RESTART on SIGALRM, too) + Fix leftright scrolling bug, when moving to a shorter line + Do validity checking on the copy, move, t command target line numbers + Change for System V % pattern broke trailing flags for empty replacement strings + Fix bug when RCM flags retained in the saved dot structure + Make the ex '=' command work for empty files + Fix bug where special_key array was being free'd (it's no longer allocated) + Matches cut in line mode only if the starting cursor is at or before the first non-blank in its line, and the ending cursor is at or after the last non-blank in its line + Add the :wn command, so you can write a file and switch to a new file in one command + Allow only a single key as an argument to :viusage + New movement code broke filter/paragraph operations in empty files ("!}date" in an empty file was dropping core) Nvi 1.12 -> Nvi 1.14: Mon Apr 18 11:05:10 1994 + Fix FILE structure leakage in the ex filter code + Rework suspend code for System V curses. Nvi has to do the the work, there's no way to get curses to do it right + Revert SunOS 4.1.X ports to the distributed curses. There's a bug in Sun's implementation that we can't live with + Quit immediately if row/column values are unreasonable + Fix the function keys to match vi historic behavior + Replace the echo/awk magic in the Makefile's with awk scripts + Version for 4.4BSD Nvi 1.11 -> Nvi 1.12: Thu Apr 14 11:10:19 1994 + Fix bug where only the first vi key was checked for validity + Make 'R' continue to overwrite after a + Only display the "no recovery" message once + Rework line backup code to restore the line to its previous condition + Don't permit :q in a .exrc file or EXINIT variable + Fix wrapscan option bug where forward searches become backward searches and do cursor correction accordingly + Change "dd" to move the cursor to the first non-blank on the line + Delete cursor attraction to the first non-blank, change non-blank motions to set the most attractive cursor position instead + Fix 'r' substitute option to set the RE to the last RE, not the last substitute RE + Fix 'c' and 'g' substitute options to always toggle, and fix edcompatible option to not reset them + Display ex error messages in inverse video + Fix errorbells option to match historic practice + Delete fixed character display table in favor of table built based on the current locale + Add ":set octal" option, that displays unknown characters as octal values instead of the default hexadecimal + Make all command and text input modes interruptible + Fix ex input mode to display error messages immediately, instead of waiting for the lines to be resolved + Fix bug where vi calling append could overwrite the command + Fix off-by-one in the ex print routine tab code + Fix incorrect ^D test in vi text input routines + Add autoindent support for ex text insert routines + Add System V substitute command replacement pattern semantics, where '%' means the last replacement pattern + Fix bug that \ didn't escape newlines in ex commands + Regularize the names of special characters to CH_* + Change hex insert character from ^Vx to ^X + Integrate System V style curses, so SunOS and Solaris ports can use the native curses implementation Nvi 1.10 -> Nvi 1.11: Thu Mar 24 16:07:45 EST 1994 + Change H, M, and L to set the absolute mark, historical practice + Fix bug in stepping through multiple tags files + Add "remapmax" option that turns off map counts so you can remap infinitely. If it's off, term_key() can be interrupted from the keyboard, which will cause the buffers to flush. I also dropped the default max number of remaps to 50. (Only Dave Hitz's TM macros and maze appear to go over that limit.) + Change :mkexrc to not dump w{300,1200,9600}, lisp options + Fix backward search within a line bug + Change all the includes of "pathnames.h" to use <>'s so that the PORT versions can use -I. to replace it with their own versions + Make reads and writes interruptible. Rework code that enters and leaves ex for '!' and filter commands, rework all interrupt and timer code + Fix core dump when user displayed option in .exrc file + Fix bug where writing empty files didn't update the saved modification time + Fix bug where /pattern/ addressing was always a backward search + Fix bug triggered by autoindent of more than 32 characters, where nvi wasn't checking the right TEXT length + Fix bug where joining only empty lines caused a core dump Nvi 1.09 -> Nvi 1.10: Sat Mar 19 15:40:29 EST 1994 + Fix "set all" core dump Nvi 1.08 -> Nvi 1.09: Sat Mar 19 10:11:14 EST 1994 + If the tag's file path is relative, and it doesn't exist, check relative to the tag file location + Fix ~ command to free temporary buffer on error return + Create vi.ref, a first cut at a reference document for vi The manual page and the reference document only document the set options, so far + Fix 1G bug not always going to the first non-blank + Upgrade PORT/regex to release alpha3.4, from Henry Spencer + Add MKS vi's "cdpath" option, supporting a cd search path + Handle if search as a motion was discarded, i.e. "d/" + Change nvi to not create multiple recovery files if modifying a recovered file + Decide to ignore that the cursor is before the '$' when inserting in list mode. It's too hard to fix Nvi 1.07 -> Nvi 1.08: Wed Mar 16 07:37:36 EST 1994 + Leftright and big line scrolling fixes. This meant more changes to the screen display code, so there may be new problems + Don't permit search-style addresses until a file has been read + "c[Ww]" command incorrectly handled the "in whitespace" case + Fix key space allocation bug triggered by cut/paste under SunOS + Ex move command got the final cursor position wrong + Delete "optimize option not implemented" message + Make the literal-next character turn off mapping for the next character in text input mode Nvi 1.06 -> Nvi 1.07: Mon Mar 14 11:10:33 EST 1994 + The "wire down" change in 1.05 broke ex command parsing, there wasn't a corresponding change to handle multiple K_VLNEXT chars + Fix final position for vi's 't' command Nvi 1.05 -> Nvi 1.06: Sun Mar 13 16:12:52 EST 1994 + Wire down ^D, ^H, ^W, and ^V, regardless of the user's termios values + Add ^D as the ex scroll command + Support ^Q as a literal-next character + Rework abbreviations to be delimited by any !inword() character + Add options description to the manual page + Minor screen cache fix for svi_get.c + Rework beautify option support to match historical practice + Exit immediately if not reading from a tty and a command fails + Default the SunOS 4.* ports to the distributed curses, not SMI's Nvi 1.04 -> Nvi 1.05: Thu Mar 24 16:07:45 EST 1994 + Make cursor keys work in input mode + Rework screen column code in vi curses screen. MAJOR CHANGE - after this, we'll be debugging curses screen presentation from scratch + Explode include files in vi.h into the source files Nvi 1.03 -> Nvi 1.04: Sun Mar 6 14:14:16 EST 1994 + Make the ex move command keep the marks on the moved lines + Change resize semantics so you can set the screen size to a specific value. A couple of screen fixes for the resize code + Fixes for foreground/background due to SIGWINCH + Complete rework of all of vi's cursor movements. The underlying assumption in the old code was that the starting cursor position was part of the range of lines cut or deleted. The command "d[[" is an example where this isn't true. Change it so that all motion component commands set the final cursor position separately from the range, as it can't be done correctly later. This is a MAJOR CHANGE - after this change, we'll be debugging the cursor positioning from scratch + Rewrite the B, b, E, e commands to use vi's getc() interface instead of rolling their own + Add a second MARK structure, LMARK, which is the larger mark needed by the logging and mark queue code. Everything else uses the reworked MARK structure, which is simply a line/column pair + Rework cut/delete to not expect 1-past-the-end in the range, but to act on text to the end of the range, inclusive + Sync on write's, to force NFS to flush Nvi 1.01 -> Nvi 1.03: Sun Jan 23 17:50:35 EST 1994 + Tag stack fixes, was returning to the tag, not the position from which the user tagged + Only use from the cursor to the end of the word in cursor word searches and tags. (Matches historical vi behavior.) + Fix delete-last-line bug when line number option set + Fix usage line for :split command + If O_NUMBER set, long input lines would eventually fail, the column count for the second screen of long lines wasn't set correctly + Fix for [[ reaching SOF with a column longer than the first line + Fix for multiple error messages if no screen displayed + Fix :read to set alternate file name as in historical practice + Fix cut to rotate the numeric buffers if line mode flag set Nvi 1.00 -> Nvi 1.01: Wed Jan 12 13:37:18 EST 1994 + Don't put cut items into numeric buffers if cutting less than parts of two lines Nvi 0.94 -> Nvi 1.00: Mon Jan 10 02:27:27 EST 1994 + Read-ahead not there; BSD tty driver problem, SunOS curses problem + Global command could error if it deleted the last line of the file + Change '.' to only apply to the 'u' if entered immediately after the 'u' command. "1pu.u.u. is still broken, but I expect that it's going to be sacrificed for multiple undo + If backward motion on a command, now move to the point; get yank cursor positioning correct + Rework cut buffers to match historic practice - yank/delete numeric buffers redone sensibly, ignoring historic practice Nvi 0.92 -> Nvi 0.93: Mon Dec 20 19:52:14 EST 1993 + Christos Zoulas reimplemented the script windows using pty's, which means that they now work reasonably. The down side of this is that almost all ports other than 4.4BSD need to include two new files, login_tty.c and pty.c from the PORT/clib directory I've added them to the Makefiles + All calloc/malloc/realloc functions now cast their pointers, for SunOS - there should be far fewer warning messages, during the build. The remaining messages are where CHAR_T's meet char *'s, i.e. where 8-bit clean meets strcmp + The user's argument list handling has been reworked so that there is always a single consistent position for use by :next, :prev and :rewind + All of the historical options are now at least accepted, although not all of them are implemented. (Edcompatible, hardtabs, lisp, optimize, redraw, and slowopen aren't implemented.) + The RE's have been reworked so that matches of length 0 are handled in the same way as vi used to handle them + Several more mapping fixes and ex parser addressing fixes # vim: set filetype=changelog ts=8 sw=8 tw=79 expandtab colorcolumn=79 : ================================================ FILE: ChangeLog.license ================================================ SPDX-License-Identifier: BSD-3-Clause Copyright (c) 2021-2024 Jeffrey H. Johnson and contributors ================================================ FILE: GNUmakefile ================================================ ############################################################################### # - O p e n V i - # ############################################################################### # vim: filetype=make:tabstop=8:tw=79:noexpandtab:colorcolumn=79:list: # SPDX-License-Identifier: BSD-3-Clause ############################################################################### ############################################################################### # # Copyright (c) 2021-2024 Jeffrey H. Johnson # # Copying and distribution of this file, with or without modification, # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. This file is offered "AS-IS", # without any warranty. # ############################################################################### ############################################################################### # Default compiler settings CC ?= cc DEPFLAGS ?= -MMD -MP INCLDS = -Iinclude -Icommon -Iregex -Iopenbsd CFLAGS += -std=gnu99 $(INCLDS) WFLAGS ?= -Wall -Wno-pointer-sign -Wno-uninitialized ############################################################################### # Set DEBUG to enable debugging build #DEBUG = 1 DBGFLAGS ?= -Wextra -ggdb -g3 -O0 ############################################################################### # Set LGC to enable link-time garbage collection (for non-debugging builds) #LGC = 1 LTGC = -fdata-sections -ffunction-sections LTGL = -Wl,--gc-sections ############################################################################### # Set LTO to enable link-time optimization (for non-debugging builds) #LTO = 1 LTOC = -flto ############################################################################### PKGCFG ?= pkg-config TR ?= tr UNAME ?= uname ifndef OS OS=$(shell $(UNAME) -s 2> /dev/null | \ $(TR) '[:upper:]' '[:lower:]' 2> /dev/null) endif # OS ifeq ($(OS),sunos) OS=$(shell $(UNAME) -o 2> /dev/null | \ $(TR) '[:upper:]' '[:lower:]' 2> /dev/null) _SUNOS = 1 endif # sunos ifeq ($(OS),os400) _OS400 = 1 OS = aix endif # os400 ############################################################################### ifeq ($(OS),solaris) ifneq (,$(findstring suncc,$(CC))) # suncc OPTLEVEL ?= -O2 _OSLCC = 1 endif # suncc else OPTLEVEL ?= -Os endif # solaris ############################################################################### ifeq ($(OS),netbsd) ifneq (,$(findstring clang,$(CC))) # clang WFLAGS += -Wno-unknown-warning-option \ -Wno-system-headers \ -Wno-char-subscripts endif # clang endif # netbsd ifeq ($(OS),illumos) WFLAGS += -Wno-unknown-pragmas endif # illumos ifeq ($(OS),solaris) ifeq ($(_OSLCC),1) SUNBITS ?= $$(command -p isainfo -b 2> /dev/null || printf '%s' 32) WFLAGS += -erroff=E_EMPTY_DECLARATION \ -erroff=E_STATEMENT_NOT_REACHED \ -erroff=E_ARG_INCOMPATIBLE_WITH_ARG_L \ -erroff=E_ASSIGNMENT_TYPE_MISMATCH \ -erroff=E_ATTRIBUTE_UNKNOWN CFLAGS += -m$(SUNBITS) LDFLAGS += -m$(SUNBITS) endif # suncc endif # solaris ############################################################################### # Try to query pkg-config for ncurses flags and libraries ifneq ($(OS),solaris) ifneq ($(OS),netbsd) ifndef CURSESLIB CFLAGS += $(shell $(PKGCFG) ncurses --cflags 2> /dev/null \ || $(PKGCFG) curses --cflags 2> /dev/null) CURSESLIB += $(shell $(PKGCFG) ncurses --libs 2> /dev/null \ || $(PKGCFG) curses --libs 2> /dev/null) endif #!CURSESLIB endif #!netbsd endif #!solaris ############################################################################### # Default libraries to link ifeq ($(OS),netbsd) CURSESLIB ?= -lcurses -lterminfo LDFLAGS += -L"/usr/local/lib" -L"/usr/pkg/lib" -L"/usr/lib" -L"/lib" LDFLAGS += -Wl,-R"/lib" -Wl,-R"/usr/lib" -Wl,-R"/usr/pkg/lib" \ -Wl,-R"/usr/local/bin" ifdef LTO ifneq (,$(findstring clang,$(CC))) # clang LLD ?= ld.lld LDFLAGS += -fuse-ld="$$(command -v $(LLD) || $(PRINTF) '%s' $(LLD))" endif # clang endif # LTO else # !netbsd CURSESLIB ?= -lncurses endif # netbsd ifeq ($(OS),aix) # aix/os400 MAIXBITS ?= $(shell command -p $(GETCONF) KERNEL_BITMODE 2> /dev/null || \ $(PRINTF) '%s' "32") ifeq ($(_OS400),1) # IBM i (OS/400) PASE CFLAGS += -I/QOpenSys/pkgs/include/ncurses LDFLAGS += -lutil -L/QOpenSys/pkgs/lib endif ifneq (,$(findstring gcc,$(CC))) # gcc (GNU C) CFLAGS += $(WFLAGS) -maix$(MAIXBITS) LDFLAGS += -maix$(MAIXBITS) -Wl,-b$(MAIXBITS) endif # gcc ifneq (,$(findstring clang,$(CC))) # xlclang / ibm-clang (IBM Open XL) CFLAGS += $(WFLAGS) -m$(MAIXBITS) LDFLAGS += -m$(MAIXBITS) -Wl,-b$(MAIXBITS) endif # clang ifneq (,$(findstring gxlc,$(CC))) # gxlc (IBM XL C) CFLAGS += -m$(MAIXBITS) LDFLAGS += -m$(MAIXBITS) -Wl,-b$(MAIXBITS) DEPFLAGS = endif # gxlc LDFLAGS += -L/opt/freeware/lib CFLAGS += -I/opt/freeware/include LINKLIBS ?= -lbsd $(CURSESLIB) -lcurses else # !aix/os400 ifeq ($(OS),solaris) CFLAGS += -U__EXTENSIONS__ -D_XPG4_2 -D__solaris__ -D_REENTRANT \ -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_XOPEN_SOURCE=600 endif # solaris ifeq ($(OS),illumos) CFLAGS += -D__illumos__ endif # illumos ifeq ($(_SUNOS),1) CFLAGS += -Du_int32_t=uint32_t -Du_int16_t=uint16_t -Du_int8_t=uint8_t CFLAGS += -DBYTE_ORDER=__BYTE_ORDER__ CFLAGS += -I/usr/include/ncurses LINKLIBS ?= $(CURSESLIB) else # !sunos LINKLIBS ?= -lutil $(CURSESLIB) endif # sunos CFLAGS += $(WFLAGS) endif # aix LINKLIBS += $(EXTRA_LIBS) ############################################################################### CFLAGS += -Ddbm_open=openbsd_dbm_open -Ddbm_close=openbsd_dbm_close \ -Ddbm_fetch=openbsd_dbm_fetch -Ddbm_firstkey=openbsd_dbm_firstkey \ -Ddbm_nextkey=openbsd_dbm_nextkey -Ddbm_delete=openbsd_dbm_delete \ -Ddbm_store=openbsd_dbm_store -Ddbm_error=openbsd_dbm_error \ -Ddbm_clearerr=openbsd_dbm_clearerr -Ddbm_rdonly=openbsd_dbm_rdonly ############################################################################### # Installation directory prefix for install/uninstall PREFIX ?= /usr/local DESTIDIR ?= ############################################################################### # Executable prefix and/or suffix (e.g. 'o', '-openbsd') for install/uninstall BINPREFIX ?= o ############################################################################### # Permissions and user:group to use for installed executables IPERM = 755 IUSGR = root:bin ############################################################################### # Using _FORTIFY_SOURCE=2 grows the binary by about ~2-3 KiB (on AMD64) ifdef DEBUG CFLAGS += $(DBGFLAGS) -DDEBUG -DSTATISTICS -DHASH_STATISTICS else # !DEBUG CFLAGS += $(OPTLEVEL) -D_FORTIFY_SOURCE=2 endif # DEBUG ############################################################################### ifndef DEBUG ifdef LTO CFLAGS += $(LTOC) LDFLAGS += $(LTOC) endif # LTO ifdef LGC CFLAGS += $(LTGC) LDFLAGS += $(LTGL) endif # LGC endif # DEBUG ############################################################################### AWK ?= awk CHMOD ?= chmod CHOWN ?= chown CP ?= cp -f PENV := env GETCONF ?= getconf LN ?= ln LNS = $(LN) -fs MKDIR ?= mkdir -p PAWK = command -p $(PENV) PATH="$$(command -p $(GETCONF) PATH)" $(AWK) PRINTF ?= printf RMDIR ?= rmdir RM ?= rm RMF = $(RM) -f SLEEP ?= sleep STRIP ?= strip SSTRIP ?= sstrip TEST ?= test TRUE ?= true UPX ?= upx ############################################################################### ifdef V DEBUG = 1 endif # V ############################################################################### ifdef DEBUG VERBOSE = set -ex else # !DEBUG VERBOSE = $(TRUE) endif # DEBUG ############################################################################### XSRC = openbsd/dirname.c \ openbsd/err.c \ openbsd/errc.c \ openbsd/errx.c \ openbsd/getopt_long.c \ openbsd/getprogname.c \ openbsd/issetugid.c \ openbsd/minpwcache.c \ openbsd/reallocarray.c \ openbsd/setmode.c \ openbsd/strlcat.c \ openbsd/strlcpy.c \ openbsd/strtonum.c \ openbsd/verr.c \ openbsd/verrc.c \ openbsd/verrx.c \ openbsd/vwarn.c \ openbsd/vwarnc.c \ openbsd/vwarnx.c \ openbsd/warn.c \ openbsd/warnc.c \ openbsd/warnx.c \ xinstall/xinstall.c SRCS = cl/cl_funcs.c \ cl/cl_main.c \ cl/cl_read.c \ cl/cl_screen.c \ cl/cl_term.c \ common/cut.c \ common/delete.c \ common/exf.c \ common/key.c \ common/line.c \ common/log.c \ common/main.c \ common/mark.c \ common/msg.c \ common/options.c \ common/options_f.c \ common/put.c \ common/recover.c \ common/screen.c \ common/search.c \ common/seq.c \ common/util.c \ db/btree/bt_close.c \ db/btree/bt_conv.c \ db/btree/bt_debug.c \ db/btree/bt_delete.c \ db/btree/bt_get.c \ db/btree/bt_open.c \ db/btree/bt_overflow.c \ db/btree/bt_page.c \ db/btree/bt_put.c \ db/btree/bt_search.c \ db/btree/bt_seq.c \ db/btree/bt_split.c \ db/btree/bt_utils.c \ db/db/db.c \ db/hash/hash_bigkey.c \ db/hash/hash_buf.c \ db/hash/hash.c \ db/hash/hash_func.c \ db/hash/hash_log2.c \ db/hash/hash_page.c \ db/hash/ndbm.c \ db/mpool/mpool.c \ db/recno/rec_close.c \ db/recno/rec_delete.c \ db/recno/rec_get.c \ db/recno/rec_open.c \ db/recno/rec_put.c \ db/recno/rec_search.c \ db/recno/rec_seq.c \ db/recno/rec_utils.c \ ex/ex_abbrev.c \ ex/ex_append.c \ ex/ex_args.c \ ex/ex_argv.c \ ex/ex_at.c \ ex/ex_bang.c \ ex/ex.c \ ex/ex_cd.c \ ex/ex_cmd.c \ ex/ex_delete.c \ ex/ex_display.c \ ex/ex_edit.c \ ex/ex_equal.c \ ex/ex_file.c \ ex/ex_filter.c \ ex/ex_global.c \ ex/ex_init.c \ ex/ex_join.c \ ex/ex_map.c \ ex/ex_mark.c \ ex/ex_mkexrc.c \ ex/ex_move.c \ ex/ex_open.c \ ex/ex_preserve.c \ ex/ex_print.c \ ex/ex_put.c \ ex/ex_quit.c \ ex/ex_read.c \ ex/ex_screen.c \ ex/ex_script.c \ ex/ex_set.c \ ex/ex_shell.c \ ex/ex_shift.c \ ex/ex_source.c \ ex/ex_stop.c \ ex/ex_subst.c \ ex/ex_tag.c \ ex/ex_txt.c \ ex/ex_undo.c \ ex/ex_usage.c \ ex/ex_util.c \ ex/ex_version.c \ ex/ex_visual.c \ ex/ex_write.c \ ex/ex_yank.c \ ex/ex_z.c \ openbsd/basename.c \ openbsd/err.c \ openbsd/errx.c \ openbsd/getopt_long.c \ openbsd/getprogname.c \ openbsd/issetugid.c \ openbsd/open.c \ openbsd/pledge.c \ openbsd/reallocarray.c \ openbsd/strlcpy.c \ openbsd/strtonum.c \ openbsd/verr.c \ openbsd/verrx.c \ openbsd/vwarn.c \ openbsd/vwarnx.c \ openbsd/warn.c \ openbsd/warnx.c \ regex/regcomp.c \ regex/regerror.c \ regex/regexec.c \ regex/regfree.c \ vi/getc.c \ vi/v_at.c \ vi/v_ch.c \ vi/v_cmd.c \ vi/v_delete.c \ vi/v_ex.c \ vi/vi.c \ vi/v_increment.c \ vi/v_init.c \ vi/v_itxt.c \ vi/v_left.c \ vi/v_mark.c \ vi/v_match.c \ vi/v_paragraph.c \ vi/v_put.c \ vi/v_redraw.c \ vi/v_replace.c \ vi/v_right.c \ vi/v_screen.c \ vi/v_scroll.c \ vi/v_search.c \ vi/v_section.c \ vi/v_sentence.c \ vi/vs_line.c \ vi/vs_msg.c \ vi/vs_refresh.c \ vi/vs_relative.c \ vi/vs_smap.c \ vi/vs_split.c \ vi/v_status.c \ vi/v_txt.c \ vi/v_ulcase.c \ vi/v_undo.c \ vi/v_util.c \ vi/v_word.c \ vi/v_xchar.c \ vi/v_yank.c \ vi/v_z.c \ vi/v_zexit.c ############################################################################### VPATH = build:cl:common:db:ex:include:vi:regex:openbsd:bin OBJS := ${SRCS:.c=.o} XOBJ := ${XSRC:.c=.o} DEPS := ${OBJS:.o=.d} XDEP := ${XOBJ:.o=.d} ############################################################################### .PHONY: all all: bin/vi \ bin/ex \ bin/view \ bin/xinstall \ docs/USD.doc/vi.man/vi.1 \ scripts/virecover.8 ############################################################################### ex/ex_def.h: ex/ex.awk ex/ex_cmd.c ifndef DEBUG -@$(PRINTF) "\r\t$(AWK):\t%42s\n" "ex/ex.awk" endif # DEBUG @$(VERBOSE); $(RMF) "./ex/ex_def.h"; \ $(PAWK) -f \ "./ex/ex.awk" "./ex/ex_cmd.c" \ > "./ex/ex_def.h" && \ $(TEST) -f \ "./ex/ex_def.h" ############################################################################### common/options_def.h: common/options.awk common/options.c ex/ex_def.h ifndef DEBUG -@$(PRINTF) "\r\t$(AWK):\t%42s\n" "command/options.awk" endif # DEBUG @$(VERBOSE); $(RMF) "./common/options_def.h"; \ $(PAWK) -f \ "./common/options.awk" "./common/options.c" \ > "./common/options_def.h" && \ $(TEST) -f "./common/options_def.h" ############################################################################### .PHONY: clean distclean realclean mostlyclean maintainer-clean ifneq (,$(findstring clean,$(MAKECMDGOALS))) .NOTPARALLEL: clean distclean realclean mostlyclean maintainer-clean endif # (,$(findstring clean,$(MAKECMDGOALS))) clean distclean realclean mostlyclean maintainer-clean: ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "common/options_def.h" endif # DEBUG @$(VERBOSE); $(RMF) "./common/options_def.h" ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "ex/ex_def.h" endif # DEBUG @$(VERBOSE); $(RMF) "./ex/ex_def.h" ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "objects" endif # DEBUG @$(VERBOSE); $(RMF) $(OBJS) $(XOBJ) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "dependencies" endif # DEBUG @$(VERBOSE); $(RMF) $(DEPS) $(XDEP) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "bin/vi" endif # DEBUG @$(VERBOSE); $(TEST) -f "./bin/vi" && $(RMF) "./bin/vi" || $(TRUE) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "bin/ex" endif # DEBUG @$(VERBOSE); $(TEST) -e "./bin/ex" && $(RMF) "./bin/ex" || $(TRUE) @$(VERBOSE); $(TEST) -h "./bin/ex" && $(RMF) "./bin/ex" || $(TRUE) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "bin/view" endif # DEBUG @$(VERBOSE); $(TEST) -e "./bin/view" && $(RMF) "./bin/view" || $(TRUE) @$(VERBOSE); $(TEST) -h "./bin/view" && $(RMF) "./bin/view" || $(TRUE) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "rm:" "bin/xinstall" endif # DEBUG @$(VERBOSE); $(TEST) -f "./bin/xinstall" && \ $(RMF) "./bin/xinstall" || $(TRUE) ifndef DEBUG -@$(PRINTF) '\r\t%s\t%42s\n' "$(RMDIR):" "bin" endif # DEBUG @$(VERBOSE); $(TEST) -d "./bin" && $(RMDIR) "./bin" || $(TRUE) -@$(TEST) -d "./bin" && $(PRINTF) '\r\t%s\t%42s\n' \ "WARNING:" "Directory './bin' not removed." || $(TRUE) ############################################################################### %.o: %.c common/options_def.h ex/ex_def.h ifndef DEBUG -@$(PRINTF) "\r\t$(CC):\t%42s\n" "$@" endif # DEBUG @$(VERBOSE); $(CC) $(CFLAGS) $(DEPFLAGS) -c -o "$@" "$<" -include $(wildcard $(DEPS)) -include $(wildcaed $(XDEP)) ############################################################################### bin/xinstall: $(XOBJ) @$(TEST) -d "./bin" || $(MKDIR) "./bin" ifndef DEBUG -@$(PRINTF) '\r\t$(LD):\t%42s\n' "$@" endif # DEBUG @$(VERBOSE); $(CC) -o "$@" $^ $(LDFLAGS) $(EXTRA_LIBS) .PHONY: xinstall xinstall: bin/xinstall -@$(TRUE) ############################################################################### bin/vi: $(OBJS) @$(TEST) -d "./bin" || $(MKDIR) "./bin" ifndef DEBUG -@$(PRINTF) '\r\t$(LD):\t%42s\n' "$@" endif # DEBUG @$(VERBOSE); $(CC) -o "$@" $^ $(LDFLAGS) $(LINKLIBS) .PHONY: vi vi: bin/vi -@$(TRUE) ############################################################################### bin/ex: bin/vi ifndef DEBUG -@$(PRINTF) "\r\t$(LN):\t%42s\n" "$@" endif # DEBUG @$(VERBOSE); $(LNS) "vi" "./bin/ex" .PHONY: ex ex: bin/ex -@$(TRUE) ############################################################################## bin/view: bin/vi ifndef DEBUG -@$(PRINTF) "\r\t$(LN):\t%42s\n" "$@" endif # DEBUG @$(VERBOSE); $(LNS) "vi" "./bin/view" .PHONY: view view: bin/view -@$(TRUE) ############################################################################### .PHONY: install ifneq (,$(findstring install,$(MAKECMDGOALS))) .NOTPARALLEL: install endif # (,$(findstring install,$(MAKECMDGOALS))) install: bin/vi bin/ex bin/view docs/USD.doc/vi.man/vi.1 \ scripts/virecover scripts/virecover.8 ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" "mkdir:" "$(DESTDIR)$(PREFIX)/bin" endif # DEBUG @$(VERBOSE); $(TEST) -d "$(DESTDIR)$(PREFIX)/bin" || \ $(MKDIR) "$(DESTDIR)$(PREFIX)/bin" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" "mkdir:" "$(DESTDIR)$(PREFIX)/libexec" endif # DEBUG @$(VERBOSE); $(TEST) -d "$(DESTDIR)$(PREFIX)/libexec" || \ $(MKDIR) "$(DESTDIR)$(PREFIX)/libexec" ifndef DEBUG @$(PRINTF) "\r\t%s\t%42s\n" \ "mkdir:" "$(DESTDIR)$(PREFIX)/share/man/man1" endif # DEBUG @$(VERBOSE); $(TEST) -d "$(DESTDIR)$(PREFIX)/share/man/man1" || \ $(MKDIR) "$(DESTDIR)$(PREFIX)/share/man/man1" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "mkdir:" "$(DESTDIR)$(PREFIX)/share/man/man8" endif # DEBUG @$(VERBOSE); $(TEST) -d "$(DESTDIR)$(PREFIX)/share/man/man8" || \ $(MKDIR) "$(DESTDIR)$(PREFIX)/share/man/man8" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "cp:" "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" endif # DEBUG @$(VERBOSE); $(CP) ./bin/vi \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" && \ $(CHOWN) "$(IUSGR)" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" && \ $(CHMOD) "$(IPERM)" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" ifndef DEBUG @$(PRINTF) "\r\t%s\t%42s\n" \ "ln:" "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)" endif # DEBUG @$(VERBOSE); $(TEST) -x \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" && \ $(LNS) "$(BINPREFIX)vi$(BINSUFFIX)" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "ln:" "$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)" endif # DEBUG @$(VERBOSE); $(TEST) -x \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" && \ $(LNS) "$(BINPREFIX)vi$(BINSUFFIX)" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "cp:" "$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)" endif # DEBUG @$(VERBOSE); $(CP) "./scripts/virecover" \ "$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)" && \ $(CHMOD) "$(IPERM)" \ "$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "cp:" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX){vi,ex,view}$(BINSUFFIX).1" endif # DEBUG @$(VERBOSE); $(CP) "docs/USD.doc/vi.man/vi.1" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1" \ && $(LNS) \ "$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1" \ && $(LNS) \ "$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1" ifndef DEBUG -@$(PRINTF) "\r\t%s\t%42s\n" \ "cp:" \ "$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8" endif # DEBUG @$(VERBOSE); $(CP) "scripts/virecover.8" \ "$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8" ############################################################################### .PHONY: install-strip installstrip ifneq (,$(findstring install-strip,$(MAKECMDGOALS))) .NOTPARALLEL: install-strip installstrip install endif # (,$(findstring install-strip,$(MAKECMDGOALS))) install-strip installstrip: install ifndef DEBUG -@$(PRINTF) "\r\t$(STRIP):\t%42s\n" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" endif # DEBUG -@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \ $(STRIP) "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" || \ $(TRUE) ############################################################################### .PHONY: strip ifneq (,$(findstring strip,$(MAKECMDGOALS))) .NOTPARALLEL: strip endif # (,$(findstring strip,$(MAKECMDGOALS))) strip: bin/vi bin/ex bin/view ifndef DEBUG -@$(PRINTF) "\r\t$(STRIP):\t%42s\n" "bin/vi" endif # DEBUG -@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \ $(STRIP) "./bin/vi" || $(TRUE) ifndef DEBUG -@$(PRINTF) "\r\t$(STRIP):\t%42s\n" "bin/xinstall" endif # DEBUG -@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \ $(STRIP) "./bin/xinstall" || $(TRUE) ############################################################################### .PHONY: superstrip sstrip ifneq (,$(findstring strip,$(MAKECMDGOALS))) .NOTPARALLEL: superstrip sstrip endif # (,$(findstring strip,$(MAKECMDGOALS))) ifneq ($(OS),freebsd) STRIP_VERS=-R '.gnu.version' else # freebsd STRIP_VERS= endif # !freebsd ifneq ($(OS),netbsd) STRIP_NOTE=-R '.note.*' else # netbsd STRIP_NOTE=-R '.SUNW_ctf' -R '.jcr' -R '.ident' \ -R '.note.netbsd.mcmodel' \ -R '.note.netbsd.pax' -R '.gnu.hash' endif # !netbsd superstrip sstrip: strip bin/vi bin/ex bin/view ifndef DEBUG -@$(PRINTF) "\r\t$(STRIP):\t%42s\n" "bin/vi" endif # DEBUG -@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \ $(STRIP) --strip-all \ -R '.gnu.build.attributes' \ -R '.eh_frame' \ -R '.eh_frame*' \ -R '.comment' $(STRIP_NOTE) \ -R '.comment.*' $(STRIP_VERS) \ "./bin/vi" \ 2> /dev/null || $(TRUE) ifndef DEBUG -@$(PRINTF) "\r\t$(SSTRIP):\t%42s\n" "bin/vi" endif # DEBUG -@$(VERBOSE); $(SSTRIP) -z "./bin/vi" \ 2> /dev/null || $(TRUE) ifndef DEBUG -@$(PRINTF) "\r\t$(STRIP):\t%42s\n" "bin/xinstall" endif # DEBUG -@$(VERBOSE); $(PENV) OBJECT_MODE=$(MAIXBITS) \ $(STRIP) --strip-all \ -R '.gnu.build.attributes' \ -R '.eh_frame' \ -R '.eh_frame*' \ -R '.comment' $(STRIP_NOTE) \ -R '.comment.*' $(STRIP_VERS) \ "./bin/xinstall" \ 2> /dev/null || $(TRUE) ifndef DEBUG -@$(PRINTF) "\r\t$(SSTRIP):\t%42s\n" "bin/xinstall" endif # DEBUG -@$(VERBOSE); $(SSTRIP) -z "./bin/xinstall" \ 2> /dev/null || $(TRUE) ############################################################################### .PHONY: upx ifneq (,$(findstring upx,$(MAKECMDGOALS))) .NOTPARALLEL: upx endif # (,$(findstring upx,$(MAKECMDGOALS))) upx: sstrip ifndef DEBUG -@$(PRINTF) "\r\t$(UPX):\t%42s\n" "bin/vi" endif # DEBUG -@$(VERBOSE); $(UPX) -qqq9 --exact "./bin/vi" 2> /dev/null || $(TRUE) -@$(VERBOSE); $(UPX) -qqq9 "./bin/vi" 2> /dev/null || $(TRUE) ifndef DEBUG -@$(PRINTF) "\r\t$(SSTRIP):\t%42s\n" "bin/vi" endif # DEBUG -@$(VERBOSE); $(SSTRIP) -z "./bin/vi" 2> /dev/null || $(TRUE) ############################################################################### .PHONY: uninstall ifneq (,$(findstring uninstall,$(MAKECMDGOALS))) .NOTPARALLEL: uninstall endif # (,$(findstring uninstall,$(MAKECMDGOALS))) uninstall: ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)vi$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)ex$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/bin/$(BINPREFIX)view$(BINSUFFIX)" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)vi$(BINSUFFIX).1" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)ex$(BINSUFFIX).1" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/share/man/man1/$(BINPREFIX)view$(BINSUFFIX).1" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/share/man/man8/$(BINPREFIX)vi.recover$(BINSUFFIX).8" ifndef DEBUG -@$(PRINTF) "\r\trm:\t%42s\n" \ "$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)" endif # DEBUG -@$(VERBOSE); $(RMF) \ "$(DESTDIR)$(PREFIX)/libexec/$(BINPREFIX)vi.recover$(BINSUFFIX)" ############################################################################### # Local Variables: # mode: make # tab-width: 8 # End: ================================================ FILE: LICENSE.md ================================================ ```text Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc. Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 1992 Keith Muller Copyright (c) 1992, 1993, 1994 Henry Spencer Copyright (c) 1994, 1996 Rob Mayoff Copyright (c) 1997 Phillip F. Knaack Copyright (c) 1997, 1998, 2002, 2004, 2015 Todd C. Miller Copyright (c) 1999, 2000, 2004, 2009, 2011 Sven Verdoolaege Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc. Copyright (c) 2002 Niels Provos Copyright (c) 2004 Ted Unangst Copyright (c) 2008 Otto Moerbeek Copyright (c) 2012, 2013 Christian Neukirchen Copyright (c) 2013 Antoine Jacoutot Copyright (c) 2014 Al Viro Copyright (c) 2015 Philip Guenther Copyright (c) 2015 The DragonFly Project Copyright (c) 2018 Duncan Overbruck Copyright (c) 2022 Ørjan Malde Copyright (c) 2021, 2022, 2023, 2024 Jeffrey H. Johnson All rights reserved. This software was originally derived from code contributed to the University of California, Berkeley by Steve Kirkendall, the author of the "Elvis" editor. This program contains code derived from software contributed to Berkeley by: * Margo Seltzer * Mike Olson * Henry Spencer, University of Totonto * Brian Hirt * Paul Vixie * David Hitz, Auspex Systems, Inc. * Dave Borman, Cray Research, Inc. * Keith Muller, UCSD This program contains code derived from software contributed to The NetBSD Foundation, Inc. by Dieter Baron, Thomas Klausner, and Jeremy C. Reed. This program contains code derived from material licensed to the University of California, Berkeley by American Telephone and Telegraph Co. or UNIX System Laboratories, Inc. and reproduced herein with the permission of UNIX System Laboratories, Inc. This program contains code developed for OpenBSD which was sponsored in part by the Defense Advanced Research Projects Agency (DARPA) and Air Force Research Laboratory, Air Force Materiel Command, United States Air Force (USAF), under agreement number F39502-99-1-0512. 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. ``` ```text In the following disclosure, the phrase "this text" refers to portions of the bundled documentation. The Institute of Electrical and Electronics Engineers and the American National Standards Committee X3 on Information Processing Systems have given permission to reprint portions of their documentation. Portions of this text are reprinted and reproduced in electronic form from IEEE Std 1003.1-1988, IEEE Standard Portable Operating System Interface for Computer Environments (POSIX), Copyright (c) 1988-1992 by The Institute of Electrical and Electronics Engineers, Inc. In the event of any discrepancy between these versions and the original IEEE Standard, the original IEEE Standard is the referee document. ``` ================================================ FILE: LICENSES/BSD-2-Clause.txt ================================================ Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc. Copyright (c) 2002 Niels Provos Copyright (c) 2022-2024 Jeffrey H. Johnson 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. THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. ================================================ FILE: LICENSES/BSD-3-Clause.txt ================================================ Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc. Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 1992 Keith Muller Copyright (c) 1992, 1993, 1994 Henry Spencer Copyright (c) 1994, 1996 Rob Mayoff Copyright (c) 1997 Phillip F. Knaack Copyright (c) 1997, 1998, 2002, 2004, 2015 Todd C. Miller Copyright (c) 1999, 2000, 2004, 2009, 2011 Sven Verdoolaege Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc. Copyright (c) 2002 Niels Provos Copyright (c) 2004 Ted Unangst Copyright (c) 2008 Otto Moerbeek Copyright (c) 2012, 2013 Christian Neukirchen Copyright (c) 2013 Antoine Jacoutot Copyright (c) 2014 Al Viro Copyright (c) 2015 Philip Guenther Copyright (c) 2015 The DragonFly Project Copyright (c) 2018 Duncan Overbruck Copyright (c) 2022 Ørjan Malde Copyright (c) 2021, 2022, 2023, 2024 Jeffrey H. Johnson All rights reserved. This software was originally derived from code contributed to the University of California, Berkeley by Steve Kirkendall, the author of the "Elvis" editor. This program contains code derived from software contributed to Berkeley by: * Margo Seltzer * Mike Olson * Henry Spencer, University of Totonto * Brian Hirt * Paul Vixie * David Hitz, Auspex Systems, Inc. * Dave Borman, Cray Research, Inc. * Keith Muller, UCSD This program contains code derived from software contributed to The NetBSD Foundation, Inc. by Dieter Baron, Thomas Klausner, and Jeremy C. Reed. This program contains code derived from material licensed to the University of California, Berkeley by American Telephone and Telegraph Co. or UNIX System Laboratories, Inc. and reproduced herein with the permission of UNIX System Laboratories, Inc. This program contains code developed for OpenBSD which was sponsored in part by the Defense Advanced Research Projects Agency (DARPA) and Air Force Research Laboratory, Air Force Materiel Command, United States Air Force (USAF), under agreement number F39502-99-1-0512. 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. ================================================ FILE: LICENSES/ISC.txt ================================================ Copyright (c) 1997, 1998, 2002, 2004, 2015 Todd C. Miller Copyright (c) 2004 Ted Unangst and Todd Miller Copyright (c) 2008 Otto Moerbeek Copyright (c) 2013 Antoine Jacoutot Copyright (c) 2015 Philip Guenther Copyright (c) 2022-2024 Jeffrey H. Johnson Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: README.md ================================================ [Uma tradução em português está disponível.](README_pt_BR.md) # OpenVi ***OpenVi*** — Portable *OpenBSD* **`vi`** / **`ex`** ## Table of Contents - [Overview](#overview) * [Why?](#why) + [Why not?](#why-not) - [Building](#building) * [Prerequisites](#prerequisites) + [Required prerequisites](#required-prerequisites) + [Optional prerequisites](#optional-prerequisites) + [Supported platforms](#supported-platforms) - [Unsupported platforms](#unsupported-platforms) * [Compilation](#compilation) + [Platform Specifics](#platform-specifics) - [AIX](#aix) - [NetBSD](#netbsd) - [illumos](#illumos) - [Solaris](#solaris) - [Windows](#windows) * [Cygwin](#cygwin) - [Availability](#availability) * [Source Code](#source-code) * [Packages](#packages) - [Versioning](#versioning) - [History](#history) - [License](#license) - [Acknowledgements](#acknowledgements) - [Similar Projects](#similar-projects) - [See Also](#see-also) ## Overview ***OpenVi*** is an enhanced and portable implementation of the Berkeley **`vi`** / **`ex`** text editor, originally developed by *Bill Joy*. ***OpenVi*** is a fork of the **`vi`** / **`ex`** editor included with *OpenBSD*, which is derived from version 1.79 of the `nvi` editor originally distributed as part of the *Fourth Berkeley Software Distribution* (**4BSD**). The **`nvi`** editor was developed by *Keith Bostic* of the *Computer Systems Research Group* (**CSRG**) at the *University of California, Berkeley*, *Sven Verdoolaege*, and other contributors. **`Nvi`** itself was derived from *Steve Kirkendall*'s **`Elvis`** editor. ### Why? Why would you want to use ***OpenVi*** instead of ***AnotherVi***? - Derived from the (extensively audited) *OpenBSD* base system code - Focus on readability, simplicity, and correctness of implementation - Adherence to *OpenBSD*'s standard secure coding practices - Uses secure functions (*e.g.* `strlcpy`, `snprintf`, `mkstemp`, `pledge`) - Reduced complexity for hopefully fewer program defects - Clean source code, distributed under a permissive 3-clause BSD license - Some support code is distributed under the (more permissive) ISC license - Mostly conforming to relevant standards (*POSIX*, *SUS*), where applicable - Enhancements, non-standard behaviors, and new features are conservatively and sanely implemented with care taken to balance user expectations, complexity, and historical accuracy - Extensions such as `bserase`, `expandtab`, `imctrl`, `visibletab`, etc. - Build requires only *GNU Make* and standard *POSIX* utilities - Easy integration with embedded, minimal, or iteratively bootstrapped environments and distributions (such as *Linux From Scratch* builds) - No compile-time or build-time configuration options - Single standard build configuration with no incompatible variants - No configuration-specific bugs resulting from untested combinations or rarely exercised code paths - Concise and understandable documentation; no subtle platform variations - Consistent user interface, script, and map behavior across all platforms - Utilizes *OpenBSD*'s extended *Spencer*-based regular expression engine (also adopted by *LLVM*, *Tcl*, etc.) on all supported systems - Single, compact, self-contained binary - No external data files required at run-time - No external library dependencies (other than curses) - Suitable for static linking and emergency “rescue” usage - All the various tweaks, fixes, improvements, and clean-ups accumulated over 25+ years as part of the *OpenBSD* base system #### Why not? So, why might you **not** want to use ***OpenVi***, then? Some of these points might be desirable features, depending on your point of view. - Internationalization support is currently lacking - No support for Unicode / UTF-8 / wide character display - Multibyte characters are shown as individual bytes, rather than glyphs - Multibyte support is planned, but is unfortunately non-trivial, see: - Schwarze, I. (2016, September 25). *Keep multibyte character support simple* [Conference presentation]. EuroBSDCon 2016 Convention, Belgrade, Serbia. [[pdf:OpenBSD](https://openbsd.org/papers/eurobsdcon2016-utf8.pdf)] - Jun-ichiro itojun Hagino [*KAME Project*] and Yoshitaka Tokugawa [*WIDE Project*]. (1999, 6 June). *Multilingual vi clones: past, now and the future* [Conference presentation]. In Proceedings of the annual conference on USENIX, Annual Technical Conference (*USENIX ATEC '99*). USENIX Association, Monterey, CA, USA, Page 45. [[doi:10.5555/1268708.1268753](https://dl.acm.org/doi/10.5555/1268708.1268753)], [[abstract:USENIX](https://www.usenix.org/conference/1999-usenix-annual-technical-conference/multilingual-vi-clones-past-now-and-future)] (*legacy*) - No support for bidirectional text - No support for regional localization or message translation - Inefficient handling of extremely large (*e.g.* multi-GB) files - No support for syntax highlighting, context-aware code completion, code folding, or “*language server*” integrations - No interactive macro recording and debugging functionality - No advanced scripting support (no *BASIC*, *COBOL*, *JavaScript*, *Lua*, *Perl*, *PHP*, *Python*, *REXX*, *Ruby*, *S-Lang*, *Tcl*, or anything else) - Only curses-based visual-mode and line-based `ex`-mode interfaces available - No support for X11/Wayland, OpenGL/Vulkan, Neuralink, augmented / virtual reality, or any other graphical user interfaces ## Building ### Prerequisites #### Required prerequisites - **POSIX.1**-**2008** environment: *POSIX* shell (`sh`) and utilities, **Awk** (`mawk`, `nawk`), etc. - **GNU Make** (version *3.81* or later) - **C99** compiler (*e.g.* `xlc`, `suncc`, `clang`, `gcc`, etc.) - **Curses** (`ncurses`, *NetBSD* `curses` V8+, `PDCurses` V2.8+, `PDCursesMod`, etc.) #### Optional prerequisites - **pkg-config** - **Perl** 5+ - **C shell** (`csh`, `tcsh`, etc.) - `nroff`, `groff`, etc. #### Supported platforms - **OpenVi** is easily portable to most platforms with *UNIX*-like operating systems that are mostly conforming to the programming interface described by *IEEE Std 1003.1-2008* and user environment described by *IEEE Std 1003.2-2008*, also known as *POSIX.1-2008* and *POSIX.2-2008*, respectively. - The following operating systems are fully supported and regularly tested using ix86/AMD64, ARM/AArch64, m68k, MIPS, POWER, and RISC-V processors: - *IBM* **AIX** 7.1+ - *Apple* **Darwin** (**macOS** / **Mac OS X**) (*ARM64*, *Intel*, *PowerPC*) - **FreeBSD** 12.3+ - **GNU**/**Linux** distributions (*glibc*, *musl*) - *illumos* **OpenIndiana** Hipster - **NetBSD** 9+ - **OpenBSD** 6.9+ - *Oracle* **Solaris** 11+ - *Microsoft* **Windows** (*Cygwin*, *Midipix*, *MSYS2*, *WSL*) - **Managarm** - The following compilers are fully supported and regularly tested: - *LLVM* **Clang** (*BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*, *Windows*) V6+ - *AMD* **Optimizing C**/**C++** (*Linux*) V3+ - *GNU* **GCC** (*AIX*, *BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*, *Windows*) V4.6+ - *IBM* **Advance Toolchain** (*Linux on POWER*) V14.0+ - *IBM* **Open XL C**/**C++** (*AIX*) V17.1+ - *IBM* **XL C**/**C++** (*AIX*, *Linux*) V16.1+ - *Intel* **oneAPI DPC++**/**C++** (*Linux*) V2021+ - *Intel* **C Compiler Classic** (*Darwin*, *Linux*) V19.1+ - *Oracle* **Developer Studio** (*Linux*, *Solaris*) V12.6+ - *PCC* **Portable C Compiler** (*NetBSD*) V1.0.0+ Newer or older operating system and compiler releases, within reason, should work. The versions listed above are those regularly tested and known working. ##### Unsupported platforms - The following platforms are **not** currently supported, but **support is planned** for a future release: - **Haiku** Walter - *SGI* **IRIX** User contributions to enhance platform support are welcomed. ### Compilation - Compilation can be performed by invoking GNU Make (usually `gmake` or `make`) from the top-level directory of a source release or git checkout. - GNU Make's `-j N` flag may be used to parallelize the compilation, where `N` is a positive integer representing the number of parallel jobs requested. - The following environment variables influence compilation and installation: - `CC` - C compiler to use - (*e.g.* `CC=gcc`) - `OPTLEVEL` - Optimization flags - (*e.g.* `OPTLEVEL=-O2`) - `CFLAGS` - Flags to pass to the C compiler - (*e.g.* `CFLAGS="-Wall -pipe"`) - `LIBS` - Libraries (overriding defaults) to pass to the linker - (*e.g.* `LIBS="-lpdcurses -lflock"`) - `LDFLAGS` - Flags to pass to the linker - (*e.g.* `LDFLAGS="-L/lib/path -static"`) - `V` - Set to enable verbose compilation output - (*e.g.* `V=1`) - `DEBUG` - Set to compile a debugging build - (*e.g.* `DEBUG=1`) - `LGC` - Set to enable link-time garbage collection - (*e.g.* `LGC=1`) - `LTO` - Set to enable link-time optimization - (*e.g.* `LTO=1`) - `EXTRA_LIBS` - Extra libraries for linking - (*e.g.* `EXTRA_LIBS=-lmtmalloc`) - `PREFIX` - Directory prefix for use with `install` and `uninstall` targets - (*e.g.* `PREFIX=/opt/OpenVi`) - The usual targets (`all`, `strip`, `superstrip`, `clean`, `distclean`, `install`, `install-strip`, `uninstall`, `upx`, etc.) are available; review the `GNUmakefile` to see all the available targets and options. For example, to compile an aggressively size-optimized build, enabling link-time optimization and link-time garbage collection, explicitly using *GCC*: ```sh env CC=gcc OPTLEVEL=-Os LGC=1 LTO=1 gmake sstrip ``` or, to verbosely compile a debugging build, explicitly using *Clang*: ```sh env CC=clang DEBUG=1 V=1 gmake ``` For systems with *GNU Make* as `make` (*e.g.* *GNU/Linux*), basic compilation should succeed without any options or additional configuration needed: ```sh make ``` With the appropriate privileges to manipulate files within the chosen `PREFIX` (using `doas`, `sudo`, `su`, etc.), the compiled executable may be installed — as-is or stripped — using an invocation such as: ```sh doas gmake install-strip ``` or ```sh sudo env PREFIX=/usr/local make install ``` #### Platform Specifics The following sections document ***only*** platform specific differences, and are not intended to be a general or exhaustive reference. For installation of prerequisite software packages or other system configuration, consult the vendor's documentation. ##### AIX - Before building ***OpenVi*** on **AIX**, install the `ncurses` libraries and headers. *IBM* provides the necessary packages, `ncurses` and `ncurses-devel`, in *RPM* format as part of the *AIX Toolbox for Linux and Open Source Software*. With the appropriate permissions (*e.g.* `root`), these packages are installable on most systems using the `dnf` or `yum` utilities, for example: ```sh dnf install ncurses ncurses-devel ``` or ```sh yum install ncurses ncurses-devel ``` The *IBM* **AIX** base system (and **PASE for i**, an integrated runtime environment for **AIX** applications on the **IBM i** operating system) provides `libxcurses`, an *XPG4*/*XSI* Extended Curses implementation derived from *AT&T System V*, which is **not** yet supported for use with ***OpenVi***. - Compilation is supported using *IBM* **XL C**/**C++** V16.1+ (`gxlc` or `xlclang`), *IBM* **Open XL C**/**C++** V17.1+ (`ibm-clang`), or *GNU* **GCC** (usually `gcc`, `gcc-8`, `gcc-9`, `gcc-10`, `gcc-11`): - Link-time optimization (`LTO=1`) requires **Open XL C**/**C++** V17.1+. The *IBM* (*AIX Toolbox*) and *Bull*/*Atos* (*Bull Freeware*) **GCC** packages, and *IBM* **XL C**/**C++** versions earlier than V17.1 are **not** LTO-enabled. - Link-time garbage collection (`LGC=1`) is **not** supported on *IBM* **AIX**. - A 64-bit build is the default on systems operating in 64-bit mode; for a 32-bit build, set the value of the `MAIXBITS` environment variable to `32` (*e.g.* `export MAIXBITS=32`). - The value of the `CC` environment variable must be set to the full path of the compiler (*e.g.* `/opt/freeware/bin/gcc`, `/opt/IBM/xlC/16.1.0/bin/gxlc`, `/opt/IBM/openxlC/17.1.0/bin/ibm-clang`, etc.) unless the compiler directory is already part of the current `PATH`. - File locking (via `flock()` as provided by the **AIX** `libbsd` library) is non-functional; this will be investigated and corrected in a future release. - ***OpenVi*** man pages are authored with `mandoc` and require conversion before use with the **AIX** `man` software (which is derived from *AT&T UNIX System V*.) ##### NetBSD - On **NetBSD** installations, the default ***OpenVi*** builds use the BSD `curses` library provided by the NetBSD base system. To use `ncurses` instead, set the values of the `CFLAGS`, `LDFLAGS`, and `CURSESLIB` environment variables appropriately (*i.e.* `CFLAGS=-I/usr/pkg/include` `LDFLAGS=-L/usr/pkg/lib` `CURSESLIB=-lncurses`). - The *LLVM* **LLD** linker is required for link-time optimization (`LTO=1`) using **Clang**. It is available as an installable package (*i.e.* `pkgin install lld`). ##### illumos - Before building ***OpenVi*** on an **illumos** distribution (*i.e.* **OpenIndiana**), install the `ncurses` libraries and headers. The **OpenIndiana** distribution provides the necessary `ncurses` package in *IPS* format. With the appropriate permissions (*e.g.* `root`), the package can be installed using the **OpenIndiana** `pkg` utility, for example: ```sh pkg install ncurses ``` The **OpenIndiana** base system provides `libcurses`, an *XPG4*/*XSI* Extended Curses implementation derived from *AT&T System V*, which is **not** yet supported for use with ***OpenVi***. - Link-time garbage collection (`LGC=1`) is **not** supported on **OpenIndiana**. ##### Solaris - Before building ***OpenVi*** on *Oracle* **Solaris** 11, install the `ncurses` libraries and headers. *Oracle* provides provides the necessary `ncurses` package for **Solaris** 11 in *IPS* format. With the appropriate permissions (*e.g.* `root`), the package can be installed using the **Solaris** `pkg` utility, for example: ```sh pkg install ncurses ``` The base *Oracle* **Solaris** system provides `libcurses`, an *XPG4*/*XSI* Extended Curses implementation derived from *AT&T System V*, which is **not** yet supported for use with ***OpenVi***. - Compilation is supported using *Oracle* **Developer Studio**, **GCC**, and **Clang**: - When using *Oracle* **Developer Studio**, invoke the compiler as `suncc` or set the value of the `_OSLCC` environment variable to `1`. - Link-time optimization (`LTO=1`) is currently supported **only** when using **GCC** or **Clang**. - Link-time garbage collection (`LGC=1`) is **not** supported on **Solaris**. - When using the *Oracle* **Developer Studio** (`suncc`) compiler, a 64-bit build is the default on systems operating in 64-bit mode; for a 32-bit build, set the value of the `SUNBITS` environment variable to `32` (*e.g.* `export SUNBITS=32`). - File locking is unavailable due to the absence of `flock()` on **Solaris**. This will be addressed by supporting *System V*-style `fcntl()` locking in a future release. ##### Windows - *Microsoft* **Windows** supports various development and runtime environments, including *MSVC*, *Cygwin*, *Midipix*, *MSYS2*, *UWIN*, the *Git Bash* environment, and others. Care must be taken to avoid mixing incompatible libraries and tools. ###### Cygwin - Compilation problems in the **Cygwin** environment are often caused by incomplete or interrupted package installations, or by the installation of packages using non-standard tools (*e.g.* `apt-cyg`), which can result in missing files and dangling or missing symbolic links. - **Before** compiling ***OpenVi*** under **Cygwin**, it is *highly* recommended to: - Update the **Cygwin** `setup.exe` application to the latest available version. - Update all installed packages using the new **Cygwin** `setup.exe` application. - Install the required prerequisite packages (*i.e.* `make`, `gcc`, `ncurses`, `ncurses-devel`) using the **Cygwin** `setup.exe` application. - Invoke the `cygcheck` utility (*i.e.* `cygcheck -cv | grep -v "OK$"`) to verify the integrity of all currently installed packages. ## Availability ### Source Code - [GitHub source repository](https://github.com/johnsonjh/OpenVi) - [Latest source release](http://github.com/johnsonjh/OpenVi/releases/latest) ### Packages **OpenVi** is available to Linux and macOS users via the [Homebrew](https://formulae.brew.sh/formula/openvi) package manager. [![Homebrew](https://repology.org/badge/version-for-repo/homebrew/openvi.svg)](https://repology.org/project/openvi/versions) ```sh brew install openvi ``` Other (unofficial) distribution packages may be available. [![Packaging status](https://repology.org/badge/vertical-allrepos/openvi.svg)](https://repology.org/project/openvi/versions) ## Versioning The ***OpenVi*** version number is based on the version of the corresponding *OpenBSD* release, followed by the ***OpenVi*** release number. The `version` command can be used to display this information in the format shown below. ```text Version 7.0.1 (OpenVi) 10/25/2021. ``` This message indicates the editor in use is ***OpenVi***, release **1**, derived from *OpenBSD* version **7.0**, and is fully synchronized with the *OpenBSD* versions of ***`vi`***, ***`ex`***, ***`db`***, and ***`regex`*** as of **10/25/2021** (*October 25th 2021*). Changes **not** derived from *OpenBSD* commits do not advance this date. New *OpenBSD* releases do not reset the ***OpenVi*** release number. ## History - ***OpenVi*** - [`ChangeLog`](/ChangeLog) - [Release history](http://github.com/johnsonjh/OpenVi/releases/) - [Commit history](https://github.com/johnsonjh/OpenVi/commits/master) - *OpenBSD* ***`vi`*** - *OpenBSD* ***`vi`*** / ***`ex`*** - [Commit history](https://github.com/openbsd/src/commits/master/usr.bin/vi) - *OpenBSD* ***`db`*** - [Commit history](https://github.com/openbsd/src/commits/master/lib/libc/db) - *OpenBSD* ***`regex`*** - [Commit history](https://github.com/openbsd/src/commits/master/lib/libc/regex) ## License - ***OpenVi*** is distributed under the terms of a **3-clause BSD** license. - See the [`LICENSE.md`](/LICENSE.md) file for the full license and distribution terms. ## Acknowledgements - *rqsd* of *Libera.Chat* for the idea that inspired the project and testing. - [*S. V. Nickolas*](https://github.com/buricco/), [*Jason Stevens*](https://virtuallyfun.com/), and the [***Virtually Fun*** *Discord*](https://discord.gg/HMwevcN) community, for support and feedback. - From the original **`vi`** acknowledgements (by *Bill Joy* & *Mark Horton*): - *Bruce Englar* encouraged the early development of this display editor. - *Peter Kessler* helped bring sanity to version 2's command layout. - *Bill Joy* wrote version **1**, versions **2.0** through **2.7**, and created the framework that users see in the present editor. - *Mark Horton* added macros and other features, and made the editor work on a large number of terminals and *UNIX* systems. - The financial support of *UUNET Communications Services* is gratefully acknowledged. ## Similar Projects - *Martin Guy*'s [**`Xvi`**](http://martinwguy.github.io/xvi/), an enhanced fork of *Tim Thompson*'s [**`STEVIE`**](https://timthompson.com/tjt/stevie/) - *S. V. Nickolas*' [**`Sivle`**](https://github.com/buricco/lunaris/tree/main/src/usr.bin/ex), a cleaned-up fork of *Steve Kirkendall*'s [**`Elvis`**](http://elvis.the-little-red-haired-girl.org/) - *Andy Valencia*'s [**`Vim57`**](https://sources.vsta.org:7100/vim57/tree), a simplified fork of version 5.7 of *Bram Moolenaar*'s [**`Vim`**](https://www.vim.org/) ## See Also - [*Carsten Kunze*'s **`vi`**](https://github.com/n-t-roff/heirloom-ex-vi/) is a currently maintained fork of the original (**1BSD**/**2BSD**) branch of the **`vi`** / **`ex`** editor, derived from *Gunnar Ritter*'s enhanced version of the [**traditional** **`vi`**](http://ex-vi.sourceforge.net/) editor. - [**`Nvi2`**](https://github.com/lichray/nvi2) is a currently maintained *feature branch* of the new (**4BSD**) version of the **`nvi`** / **`nex`** editor, with a focus on extensibility and new features. - [**`Nvi1`**](https://repo.or.cz/nvi.git) (*version* *1.8+*) is the currently maintained *traditional branch* of the new (**4BSD**) version of the **`nvi`** / **`nex`** editor, now developed by *Sven Verdoolaege*. ================================================ FILE: README_pt_BR.md ================================================ [An English version of this README is available.](README.md) # OpenVi ***OpenVi*** — Portable *OpenBSD* **`vi`** / **`ex`** ## Índice - [Visão geral](#visão-geral) * [Porquê?](#porquê) + [Por que não?](#por-que-não) - [Construindo](#building) * [Pré-requisitos](#pré-requisitos) + [Pré-requisitos necessários](#pré-requisitos-necessários) + [Pré-requisitos opcionais](#pré-requisitos-opcionais) + [Plataformas suportadas](#plataformas-suportadas) - [Plataformas não suportadas](#plataformas-não-suportadas) * [Compilação](#compilação) + [Especificações da plataforma](#especificações-da-plataforma) - [AIX](#aix) - [NetBSD](#netbsd) - [illumos](#illumos) - [Solaris](#solaris) - [Windows](#windows) * [Cygwin](#cygwin) - [Disponibilidade](#disponibilidade) * [Códgo fonte](#código-fonte) * [Pacotes](#pacotes) - [Versionamento](#versionamento) - [Histórico](#histórico) - [Licença](#licença) - [Agradecimentos](#agradecimentos) - [Projetos similares](#projetos-similares) - [Veja também](#veja-também) ## Visão geral ***OpenVi*** é uma implementação aprimorada e portátil do Berkeley **`vi`** / **`ex`** editor de texto, originalmente desenvolvido por *Bill Joy*. ***OpenVi*** é um fork do editor **`vi`** / **`ex`** incluído no *OpenBSD*, que é derivado da versão 1.79 do editor `nvi` originalmente distribuído como parte da *Fourth Berkeley Software Distribution* (**4BSD**). O editor **`nvi`** foi desenvolvido por *Keith Bostic* da *Computer Systems Research Group* (**CSRG**) na *Universidade da Califórnia, Berkeley*, *Sven Verdoolaege* e outros colaboradores. O próprio **`Nvi`** foi derivado de *Steve O editor do **`Elvis`** de Kirkendall*. ### Porquê? Por que você deveria usar ***OpenVi*** em vez de ***AnotherVi***? - Derivado do código do sistema base *OpenBSD* (extensivamente auditado) - Foco na legibilidade, simplicidade e correção da implementação - Aderência às práticas padrão de codificação segura do *OpenBSD* - Usa funções seguras (*por exemplo* `strlcpy`, `snprintf`, `mkstemp`, `pledge`) - Complexidade reduzida para menos defeitos de programa - Código-fonte limpo, distribuído sob uma licença BSD permissiva de 3 cláusulas - Código de suporte é distribuído sob a licença ISC (mais permissiva) - Principalmente em conformidade com os padrões relevantes (*POSIX*, *SUS*), quando aplicável - Aprimoramentos, comportamentos fora do padrão e novos recursos são conservadoramente e implementados de forma sensata com cuidado para equilibrar as expectativas do usuário, complexidade e precisão histórica - Extensões como `bserase`, `expandtab`, `imctrl`, `visibletab`, etc. - A compilação requer apenas utilitários *GNU Make* e *POSIX* padrão - Fácil integração com bootstrap incorporado, mínimo ou iterativo ambientes e distribuições (como compilações *Linux From Scratch*) - Sem opções de configuração em tempo de compilação ou em tempo de compilação - Configuração de compilação padrão única sem variantes incompatíveis - Nenhum bug específico de configuração resultante de combinações não testadas ou caminhos de código raramente exercitados - Documentação concisa e compreensível; sem variações sutis de plataforma - Interface de usuário, script e comportamento de mapa consistentes em todas as plataformas - Utiliza o mecanismo de expressão regular estendido baseado em *Spencer* do *OpenBSD* (também adotado por *LLVM*, *Tcl*, etc.) em todos os sistemas suportados - Binário único, compacto e independente - Não são necessários arquivos de dados externos em tempo de execução - Sem dependências de bibliotecas externas (além de curses) - Adequado para ligação estática e uso de “resgate” de emergência - Todos os vários ajustes, correções, melhorias e limpezas acumuladas mais de 25 anos como parte do sistema base *OpenBSD* #### Por que não? Então, por que você **não** quer usar o ***OpenVi***? Alguns desses pontos podem ser características desejáveis, dependendo do seu ponto de vista. - O suporte à internacionalização está faltando no momento - Sem suporte para Unicode / UTF-8 / exibição de caracteres amplos - Os caracteres multibyte são mostrados como bytes individuais, em vez de glifos - O suporte multibyte está planejado, mas infelizmente não é trivial, veja: - Schwarze, I. (2016, 25 de setembro). *Mantenha o suporte a caracteres multibyte simples* [Apresentação em conferência]. Convenção EuroBSDCon 2016, Belgrado, Sérvia. [[pdf:OpenBSD](https://openbsd.org/papers/eurobsdcon2016-utf8.pdf)] - Jun-ichiro itojun Hagino [*KAME Project*] e Yoshitaka Tokugawa [*Projeto WIDE*]. (1999, 6 de junho). *Clones vi multilíngues: passado, agora e o futuro* [Apresentação em conferência]. Em Anais do conferência anual sobre USENIX, Conferência Técnica Anual (*USENIX ATEC '99*). Associação USENIX, Monterey, CA, EUA, página 45. [[doi:10.5555/1268708.1268753](https://dl.acm.org/doi/10.5555/1268708.1268753)], [[resumo: USENIX](https://www.usenix.org/conference/1999-usenix-annual-technical-conference/multilingual-vi-clones-past-now-and-future)] (*legado*) - Sem suporte para texto bidirecional - Sem suporte para localização regional ou tradução de mensagens - Manuseio ineficiente de arquivos extremamente grandes (*por exemplo* multi-GB) - Sem suporte para realce de sintaxe, conclusão de código com reconhecimento de contexto, código integrações de dobramento ou “*servidor de idiomas*” - Nenhuma funcionalidade interativa de gravação e depuração de macro - Sem suporte de script avançado (sem *BASIC*, *COBOL*, *JavaScript*, *Lua*, *Perl*, *PHP*, *Python*, *REXX*, *Ruby*, *S-Lang*, *Tcl* ou qualquer outro) - Somente interfaces de modo visual baseado em maldições e modo `ex` baseado em linha disponíveis - Sem suporte para X11/Wayland, OpenGL/Vulkan, Neuralink, realidade aumentada/virtual ou qualquer outra interface gráfica do usuário ## Construind ### Pré-requisitos #### Pré-requisitos necessários - ambiente **POSIX.1**-**2008**: shell *POSIX* (`sh`) e utilitários, **Awk** (`mawk`, `nawk`), etc. - **GNU Make** (versão *3.81* ou posterior) - Compilador **C99** (*por exemplo* `xlc`, `suncc`, `clang`, `gcc`, etc.) - **Curses** (`ncurses`, *NetBSD* `curses` V8+, `PDCurses` V2.8+, `PDCursesMod`, etc.) #### Pré-requisitos opcionais - **pkg-config** - **Perl** 5+ - **Cshell** (`csh`, `tcsh`, etc.) - `nroff`, `groff`, etc. #### Plataformas suportadas - **OpenVi** é facilmente portátil para a maioria das plataformas com operação semelhante ao *UNIX* sistemas que estão em conformidade com a interface de programação descrita por *IEEE Std 1003.1-2008* e ambiente do usuário descrito por *IEEE Std 1003.2-2008*, também conhecido como *POSIX.1-2008* e *POSIX.2-2008*, respectivamente. - Os seguintes sistemas operacionais são totalmente suportados e testados regularmente usando processadores ix86/AMD64, ARM/AArch64, m68k, MIPS, POWER e RISC-V: - *IBM* **AIX** 7.1+ - *Apple* **Darwin** (**macOS** / **Mac OS X**) (*ARM64*, *Intel*, *PowerPC*) - **FreeBSD** 12.3+ - Distribuições **GNU**/**Linux** (*glibc*, *musl*) - *illumos* **OpenIndiana** Hipster - **NetBSD** 9+ - **OpenBSD** 6.9+ - *OracleSolaris** 11+ - *Microsoft* **Windows** (*Cygwin*, *Midipix*, *MSYS2*, *WSL*) - **Managarm** - Os seguintes compiladores são totalmente suportados e testados regularmente: - *LLVM* **Clang** (*BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*, *Windows*) V6+ - *AMD* **Optimizing C**/**C++** (*Linux*) V3+ - *GNU* **GCC** (*AIX*, *BSD*, *Darwin*, *illumos*, *Linux*, *Solaris*, *Windows*) V4.6+ - *IBM* **Advance Toolchain** (*Linux on POWER*) V14.0+ - *IBM* **Open XL C**/**C++** (*AIX*) V17.1+ - *IBM* **XL C**/**C++** (*AIX*, *Linux*) V16.1+ - *Intel* **oneAPI DPC++**/**C++** (*Linux*) V2021+ - *Intel* **C Compiler Classic** (*Darwin*, *Linux*) V19.1+ - *Oracle* **Developer Studio** (*Linux*, *Solaris*) V12.6+ - *PCC* **Portable C Compiler** (*NetBSD*) V1.0.0+ Versões de sistema operacional e compilador mais recentes ou mais antigas, dentro do razoável, devem trabalhar. As versões listadas acima são aquelas regularmente testadas e comprovadamente funcionando. ##### Plataformas não suportadas - As seguintes plataformas **não** são suportadas atualmente, mas **suporte é planejado** para um lançamento futuro: - **Haicai** Walter - *SGI* **IRIX** Contribuições de usuários para melhorar o suporte da plataforma são bem-vindas. ### Compilação - A compilação pode ser realizada invocando o GNU Make (geralmente `gmake` ou `make`) do diretório de nível superior de uma versão de código-fonte ou git checkout. - O sinalizador `-j N` do GNU Make pode ser usado para paralelizar a compilação, onde `N` é um número inteiro positivo que representa o número de tarefas paralelas solicitadas. - As seguintes variáveis ​​de ambiente influenciam a compilação e instalação: - `CC` - Compilador C a ser usado - (*ex.* ​​`CC=gcc`) - `OPTLEVEL` - sinalizadores de otimização - (*por exemplo* `OPTLEVEL=-O2`) - `CFLAGS` - Flags para passar para o compilador C - (*por exemplo* `CFLAGS="-Wall -pipe"`) - `LIBS` - Bibliotecas (substituindo padrões) para passar para o vinculador - (*por exemplo* `LIBS="-lpdcurses -lflock"`) - `LDFLAGS` - Flags para passar para o linker - (*por exemplo* `LDFLAGS="-L/lib/path -static"`) - `V` - Definido para ativar a saída de compilação detalhada - (*por exemplo* `V=1`) - `DEBUG` - Definido para compilar uma compilação de depuração - (*ex.* ​​`DEBUG=1`) - `LGC` - Definido para habilitar a coleta de lixo de tempo de link - (*por exemplo* `LGC=1`) - `LTO` - Definido para ativar a otimização de tempo de link - (*por exemplo* `LTO=1`) - `EXTRA_LIBS` - Bibliotecas extras para vinculação - (*ex.* ​​`EXTRA_LIBS=-lmtmalloc`) - `PREFIX` - Prefixo de diretório para uso com alvos `install` e `uninstall` - (*ex.* ​​`PREFIX=/opt/OpenVi`) - Os alvos usuais (`all`, `strip`, `superstrip`, `clean`, `distclean`, `install`, `install-strip`, `uninstall`, `upx`, etc.) estão disponíveis; Reveja o `GNUmakefile` para ver todos os alvos e opções disponíveis. Por exemplo, para compilar uma compilação de depuração de tamanho agressivamente otimizado, permitindo otimização de tempo de link e coleta de lixo de tempo de link, usando explicitamente *CCG*: ```sh env CC=gcc OPTLEVEL=-Os LGC=1 LTO=1 gmake sstrip ``` ou, para compilar detalhadamente uma compilação de depuração, explicitamente usando *Clang*: ```sh env CC=clang DEBUG=1 V=1 gmake ``` Para sistemas com *GNU Make* como `make` (*por exemplo* *GNU/Linux*), compilação básica deve ser bem-sucedido sem nenhuma opção ou configuração adicional necessária: ```sh make ``` Com os privilégios apropriados para manipular arquivos dentro do `PREFIX` escolhido (usando `doas`, `sudo`, `su`, etc.), o executável compilado pode ser instalado — como está ou despojado — usando uma invocação como: ```sh doas gmake install-strip ``` ou ```sh sudo env PREFIX=/usr/local make install ``` #### Especificações da plataforma As seções a seguir documentam ***apenas*** diferenças específicas de plataforma e não pretendem ser uma referência geral ou exaustiva. Para instalação de pacotes de software de pré-requisito ou outra configuração do sistema, consulte a documentação do fornecedor. ##### AIX - Antes de compilar o ***OpenVi*** no **AIX**, instale as bibliotecas `ncurses` e cabeçalhos. *IBM* fornece os pacotes necessários, `ncurses` e `ncurses-devel`, no formato *RPM* como parte do *AIX Toolbox for Linux e Software livre*. Com as permissões apropriadas (*por exemplo* `root`), estes os pacotes são instaláveis ​​na maioria dos sistemas usando os utilitários `dnf` ou `yum`, por exemplo: ```sh dnf install ncurses ncurses-devel ``` ou ```sh yum install ncurses ncurses-devel ``` O sistema base *IBM* **AIX** (e **PASE for i**, um sistema de tempo de execução integrado ambiente para aplicativos **AIX** no sistema operacional **IBM i**) fornece `libxcurses`, uma implementação *XPG4*/*XSI* Extended Curses derivado do *AT&T System V*, que **ainda não** é suportado para uso com ***OpenVi***. - A compilação é suportada usando *IBM* **XL C**/**C++** V16.1+ (`gxlc` ou `xlclang`), *IBM* **Open XL C**/**C++** V17.1+ (`ibm-clang`), ou *GNU* **GCC** (geralmente `gcc`, `gcc-8`, `gcc-9`, `gcc-10`, `gcc-11`): - Otimização de tempo de link (`LTO=1`) requer **Open XL C**/**C++** V17.1+. O *IBM* (*AIX Toolbox*) e *Bull*/*Atos* (*Bull Freeware*) **GCC** pacotes e versões *IBM* **XL C**/**C++** anteriores à V17.1 são **não** habilitado para LTO. - A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada no *IBM* **AIX**. - Uma compilação de 64 bits é o padrão em sistemas operando no modo de 64 bits; para compilação de 32 bits, defina o valor da variável de ambiente `MAIXBITS` para `32` (*ex.* ​​`export MAIXBITS=32`). - O valor da variável de ambiente `CC` deve ser definido como o caminho completo do compilador (*por exemplo* `/opt/freeware/bin/gcc`, `/opt/IBM/xlC/16.1.0/bin/gxlc`, `/opt/IBM/openxlC/17.1.0/bin/ibm-clang`, etc.) a menos que o diretório do compilador já faça parte do `PATH` atual. - Bloqueio de arquivo (via `flock()` conforme fornecido pela biblioteca `libbsd` do **AIX**) é não funcional; isso será investigado e corrigido em uma versão futura. - As páginas man ***OpenVi*** são criadas com `mandoc` e requerem conversão antes de usar com o software `man` do **AIX** (que é derivado do *AT&T Sistema UNIX V*.) ##### NetBSD - Nas instalações do **NetBSD**, as compilações padrão do ***OpenVi*** usam a biblioteca BSD `curses` fornecida pelo sistema base do NetBSD. Para usar `ncurses`, defina os valores de `CFLAGS`, `LDFLAGS` e `CURSESLIB` variáveis ​​de ambiente apropriadamente (*isto é* `CFLAGS=-I/usr/pkg/include` `LDFLAGS=-L/usr/pkg/lib` `CURSESLIB=-lncurses`). - O linker *LLVM* **LLD** é necessário para otimização de tempo de link (`LTO=1`) usando **Clang**. Está disponível como um pacote instalável (*i.e.* `pkgin instalar lld`). ##### illumos - Antes de compilar o ***OpenVi*** em uma distribuição **illumos** (*i.e.* **OpenIndiana**), instale as bibliotecas e cabeçalhos `ncurses`. A distribuição **OpenIndiana** fornece o pacote `ncurses` necessário no formato *IPS*. Com as permissões apropriadas (*por exemplo* `root`), o pacote pode ser instalado usando o utilitário `pkg` **OpenIndiana**, por exemplo: ```sh pkg install ncurses ``` O sistema base **OpenIndiana** fornece `libcurses`, um *XPG4*/*XSI* Implementação Extended Curses derivada do *AT&T System V*, que **não** ainda suportado para uso com ***OpenVi***. - A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada em **OpenIndiana**. ##### Solaris - Antes de compilar o ***OpenVi*** no *Oracle* **Solaris** 11, instale as bibliotecas e cabeçalhos `ncurses`. *Oracle* fornece o pacote `ncurses` necessário para **Solaris** 11 no formato *IPS*. Com as permissões apropriadas (*por exemplo* `root`), o pacote pode ser instalado usando o utilitário **Solaris** `pkg`, por exemplo: ```sh pkg install ncurses ``` O sistema base *Oracle* **Solaris** fornece `libcurses`, uma *XPG4*/*XSI* Implementação Extended Curses derivada do *AT&T System V*, que ainda **não** não é suportado para uso com ***OpenVi***. - A compilação é suportada usando *Oracle* **Developer Studio**, **GCC** e **Clang**: - Ao usar *Oracle* **Developer Studio**, invoque o compilador como `suncc` ou defina o valor da variável de ambiente `_OSLCC` para `1`. - A otimização de tempo de link (`LTO=1`) é atualmente suportada **apenas** ao usar **GCC** ou **Clang**. - A coleta de lixo em tempo de link (`LGC=1`) **não** é suportada no **Solaris**. - Ao usar o compilador *Oracle* **Developer Studio** (`suncc`), uma build 64-bit é o padrão em sistemas operando no modo de 64 bits; para uma build em 32 bits, defina o valor da variável de ambiente `SUNBITS` para `32` (*ex.* `export SUNBITS=32`). - O bloqueio de arquivo não está disponível devido à ausência de `flock()` no **Solaris**. Isso será resolvido com o suporte ao bloqueio `fcntl()` estilo *System V* em um lançamento futuro. ##### Windows - *Microsoft* **Windows** suporta vários desenvolvimentos e tempo de execução, incluindo *MSVC*, *Cygwin*, *Midipix*, *MSYS2*, *UWIN*, o ambiente *Git Bash* e outros. Deve-se tomar cuidado para evitar a mistura de bibliotecas e ferramentas incompatíveis. ###### Cygwin - Problemas de compilação no ambiente **Cygwin** geralmente são causados ​​por instalações de pacotes incompletos ou interrompidos, ou pela instalação de pacotes usando ferramentas não padrão (*por exemplo* `apt-cyg`), o que pode resultar em arquivos ausentes e links simbólicos pendentes ou ausentes. - **Antes** de compilar ***OpenVi*** em **Cygwin**, é *altamente* recomendado que: - Atualize o aplicativo **Cygwin** `setup.exe` para a última versão disponível. - Atualize todos os pacotes instalados usando a nova aplicação **Cygwin** `setup.exe` - Instale os pacotes de pré-requisito necessários (*ou seja,* `make`, `gcc`, `ncurses`, `ncurses-devel`) usando o aplicativo **Cygwin** `setup.exe`. - Invoque o utilitário `cygcheck` (*i.e.* `cygcheck -cv | grep -v "OK$"`) para verificar a integridade de todos os pacotes atualmente instalados. ## Disponibilidade ### Código fonte - [Repositório de origem do GitHub](https://github.com/johnsonjh/OpenVi) - [Lançamento da fonte mais recente](http://github.com/johnsonjh/OpenVi/releases/latest) ### Pacotes **OpenVi** está disponível para usuários de Linux e macOS por meio do Gerenciador de pacotes [Homebrew](https://formulae.brew.sh/formula/openvi). [![Homebrew](https://repology.org/badge/version-for-repo/homebrew/openvi.svg)](https://repology.org/project/openvi/versions) ```sh brew install openvi ``` Outros pacotes de distribuição (não oficiais) podem estar disponíveis. [![Status da embalagem](https://repology.org/badge/vertical-allrepos/openvi.svg)](https://repology.org/project/openvi/versions) ## Versionamento O número da versão do ***OpenVi*** é baseado na versão do lançamento do *OpenBSD* correspondente, seguido pelo número do lançamento do ***OpenVi***. O comando `version` pode ser usado para exibir esta informação no formato mostrado abaixo. ```text Version 7.0.1 (OpenVi) 10/25/2021. ``` Esta mensagem indica que o editor em uso é ***OpenVi***, versão **1**, derivado do *OpenBSD* versão **7.0**, e é totalmente sincronizado com as versões *OpenBSD* de ***`vi`***, ***`ex`***, ***`db`***, e ***`regex`*** a partir de **10/25/2021** (*25 de outubro de 2021*). Alterações **não** derivadas de commits do *OpenBSD* não avançam nesta data. Novos lançamentos do *OpenBSD* não redefinem o número do lançamento do ***OpenVi***. ## Histórico - ***OpenVi*** - [`ChangeLog`](/ChangeLog) - [Histórico de lançamento](http://github.com/johnsonjh/OpenVi/releases/) - [Histórico de commits](https://github.com/johnsonjh/OpenVi/commits/master) - *OpenBSD* ***`vi`*** - *OpenBSD* ***`vi`*** / ***`ex`*** - [Histórico de commits](https://github.com/openbsd/src/commits/master/usr.bin/vi) - *OpenBSD* ***`db`*** - [Histórico de commits](https://github.com/openbsd/src/commits/master/lib/libc/db) - *OpenBSD* ***`regex`*** - [Histórico de commits](https://github.com/openbsd/src/commits/master/lib/libc/regex) ## Licença - ***OpenVi*** é distribuído sob os termos de uma licença **3-clause BSD**. - Veja o arquivo [`LICENSE.md`](/LICENSE.md) para a licença completa e termos de distribuição. ## Agradecimentos - *rqsd* de *Libera.Chat* pela ideia que inspirou o projeto e os testes. - [*S. V. Nickolas*](https://github.com/buricco/), [*Jason Stevens*](https://virtuallyfun.com/), e o [***Virtually Fun*** *Discord*](https://discord.gg/HMwevcN) comunidade, pelo suporte e feedback. - Dos agradecimentos originais do **`vi`** (por *Bill Joy* e *Mark Horton*): - *Bruce Englar* encorajou o desenvolvimento inicial deste editor de exibição. - *Peter Kessler* ajudou a trazer sanidade ao layout de comando da versão 2. - *Bill Joy* escreveu a versão **1**, versões **2.0** até **2.7** e criou a estrutura que os usuários veem no editor atual. - *Mark Horton* adicionou macros e outros recursos e fez o editor funcionar em um grande número de terminais e sistemas *UNIX*. - Agradecemos o apoio financeiro dos *UUNET Communications Services*. ## Projetos Similares - [**`Xvi`**](http://martinwguy.github.io/xvi/) de *Martin Guy*, uma versão melhorada de [**`STEVIE`**]( de *Tim Thompson* https://timthompson.com/tjt/stevie/) - *S. V. Nickolas*' [**`Sivle`**](https://github.com/buricco/lunaris/tree/main/src/usr.bin/ex), um fork limpo de *Steve Kirkendall* [**`Elvis`**](http://elvis.the-little-red-haired-girl.org/) - *Andy Valencia*'s [**`Vim57`**](https://sources.vsta.org:7100/vim57/tree), um fork simplificado da versão 5.7 de *Bram Moolenaar* [**`Vim`**](https://www.vim.org/) ## Veja também - [*Carsten Kunze*'s **`vi`**](https://github.com/n-t-roff/heirloom-ex-vi/) é um fork atualmente mantido do ramo original (**1BSD**/**2BSD**) do editor **`vi`** / **`ex`**, derivado do editor aprimorado de *Gunnar Ritter* versão do [**tradicional** do editor **`vi`**](http://ex-vi.sourceforge.net/). - [**`Nvi2`**](https://github.com/lichray/nvi2) é um *branch de recursos* atualmente mantido da nova versão (**4BSD**) do editor **`nvi`* * / **`nex`**, com foco em extensibilidade e novas funcionalidades. - [**`Nvi1`**](https://repo.or.cz/nvi.git) (*versão* *1.8+*) é o *branch tradicional* atualmente mantido do novo (**4BSD* *) versão do editor **`nvi`** / **`nex`**, agora desenvolvido por *Sven Verdoolaege*. ================================================ FILE: REUSE.toml ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2021-2024 Jeffrey H. Johnson version = 1 SPDX-PackageName = "OpenVi" SPDX-PackageSupplier = "Jeffrey H. Johnson " SPDX-PackageDownloadLocation = "https://github.com/johnsonjh/OpenVi" annotations = [] ================================================ FILE: cl/cl.h ================================================ /* $OpenBSD: cl.h,v 1.12 2021/09/02 11:19:02 schwarze Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)cl.h 10.19 (Berkeley) 9/24/96 */ extern volatile sig_atomic_t cl_sigint; extern volatile sig_atomic_t cl_sigterm; extern volatile sig_atomic_t cl_sigwinch; typedef struct _cl_private { CHAR_T ibuf[512]; /* Input keys. */ int eof_count; /* EOF count. */ struct termios orig; /* Original terminal values. */ struct termios ex_enter;/* Terminal values to enter ex. */ struct termios vi_enter;/* Terminal values to enter vi. */ char *el; /* Clear to EOL terminal string. */ char *cup; /* Cursor movement terminal string. */ char *cuu1; /* Cursor up terminal string. */ char *rmso, *smso; /* Inverse video terminal strings. */ char *smcup, *rmcup; /* Terminal start/stop strings. */ #define INDX_HUP 0 #define INDX_INT 1 #define INDX_TERM 2 #define INDX_WINCH 3 #define INDX_MAX 4 /* Original signal information. */ struct sigaction oact[INDX_MAX]; enum { /* Tty group write mode. */ TGW_UNKNOWN=0, TGW_SET, TGW_UNSET } tgw; enum { /* Terminal initialization strings. */ TE_SENT=0, TI_SENT } ti_te; #define CL_IN_EX 0x0001 /* Currently running ex. */ #define CL_RENAME 0x0002 /* X11 xterm icon/window renamed. */ #define CL_RENAME_OK 0x0004 /* User wants the windows renamed. */ #define CL_SCR_EX_INIT 0x0008 /* Ex screen initialized. */ #define CL_SCR_VI_INIT 0x0010 /* Vi screen initialized. */ #define CL_STDIN_TTY 0x0020 /* Talking to a terminal. */ u_int32_t flags; } CL_PRIVATE; #define CLP(sp) ((CL_PRIVATE *)((sp)->gp->cl_private)) #define GCLP(gp) ((CL_PRIVATE *)(gp)->cl_private) /* Return possibilities from the keyboard read routine. */ typedef enum { INP_OK=0, INP_EOF, INP_ERR, INP_INTR, INP_TIMEOUT } input_t; /* The screen line relative to a specific window. */ #define RLNO(sp, lno) (sp)->woff + (lno) /* X11 xterm escape sequence to rename the icon/window. */ #define XTERM_RENAME "\033]0;%s\007" #include "cl_extern.h" ================================================ FILE: cl/cl_extern.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ int cl_addstr(SCR *, const char *, size_t); int cl_attr(SCR *, scr_attr_t, int); int cl_baud(SCR *, unsigned long *); int cl_bell(SCR *); int cl_clrtoeol(SCR *); int cl_cursor(SCR *, size_t *, size_t *); int cl_deleteln(SCR *); int cl_ex_adjust(SCR *, exadj_t); int cl_insertln(SCR *); int cl_keyval(SCR *, scr_keyval_t, CHAR_T *, int *); int cl_move(SCR *, size_t, size_t); int cl_refresh(SCR *, int); int cl_rename(SCR *, char *, int); int cl_suspend(SCR *, int *); void cl_usage(void); int sig_init(GS *, SCR *); int cl_event(SCR *, EVENT *, u_int32_t, int); int cl_screen(SCR *, u_int32_t); int cl_quit(GS *); int cl_getcap(SCR *, char *, char **); int cl_term_init(SCR *); int cl_term_end(GS *); int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); int cl_optchange(SCR *, int, char *, unsigned long *); int cl_omesg(SCR *, CL_PRIVATE *, int); int cl_ssize(SCR *, int, size_t *, size_t *, int *); int cl_putchar(int); ================================================ FILE: cl/cl_funcs.c ================================================ /* $OpenBSD: cl_funcs.c,v 1.23 2022/12/26 19:16:03 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #ifndef __solaris__ # include # include #endif /* ifndef __solaris__ */ #include #include #include #include #include #include #include #ifdef __solaris__ # define _XPG7 # undef __EXTENSIONS__ # include #endif /* ifdef __solaris__ */ #include #include #include #ifdef __solaris__ # undef GS #endif /* ifdef __solaris__ */ #include "../common/common.h" #include "../vi/vi.h" #include "cl.h" /* * cl_addstr -- * Add len bytes from the string at the cursor, advancing the cursor. * * PUBLIC: int cl_addstr(SCR *, const char *, size_t); */ int cl_addstr(SCR *sp, const char *str, size_t len) { size_t oldy, oldx; int iv; (void)oldx; (void)oldy; /* * If ex isn't in control, it's the last line of the screen and * it's a split screen, use inverse video. */ iv = 0; getyx(stdscr, oldy, oldx); if (!F_ISSET(sp, SC_SCR_EXWROTE) && oldy == RLNO(sp, LASTLINE(sp)) && IS_SPLIT(sp)) { iv = 1; (void)standout(); } if (addnstr(str, len) == ERR) return (1); if (iv) (void)standend(); return (0); } /* * cl_attr -- * Toggle a screen attribute on/off. * * PUBLIC: int cl_attr(SCR *, scr_attr_t, int); */ int cl_attr(SCR *sp, scr_attr_t attribute, int on) { CL_PRIVATE *clp; clp = CLP(sp); switch (attribute) { case SA_ALTERNATE: /* * !!! * There's a major layering violation here. The problem is that the * X11 xterm screen has what's known as an "alternate" screen. Some * xterm termcap/terminfo entries include sequences to switch to/from * that alternate screen as part of the ti/te (smcup/rmcup) strings. * Vi runs in the alternate screen, so that you are returned to the * same screen contents on exit from vi that you had when you entered * vi. Further, when you run :shell, or :!date or similar ex commands, * you also see the original screen contents. This wasn't deliberate * on vi's part, it's just that it historically sent terminal init/end * sequences at those times, and the addition of the alternate screen * sequences to the strings changed the behavior of vi. The problem * caused by this is that we don't want to switch back to the alternate * screen while getting a new command from the user, when the user is * continuing to enter ex commands, e.g.: * * :!date <<< switch to original screen * [Hit return to continue] <<< prompt user to continue * :command <<< get command from user * * Note that the :command input is a true vi input mode, e.g., input * maps and abbreviations are being done. So, we need to be able to * switch back into the vi screen mode, without flashing the screen. * * To make matters worse, the curses initscr() and endwin() calls will * do this automatically -- so, this attribute isn't as controlled by * the higher level screen as closely as one might like. */ if (on) { if (clp->ti_te != TI_SENT) { clp->ti_te = TI_SENT; if (clp->smcup == NULL) (void)cl_getcap(sp, "smcup", &clp->smcup); if (clp->smcup != NULL) (void)tputs(clp->smcup, 1, cl_putchar); } } else if (clp->ti_te != TE_SENT) { clp->ti_te = TE_SENT; if (clp->rmcup == NULL) (void)cl_getcap(sp, "rmcup", &clp->rmcup); if (clp->rmcup != NULL) (void)tputs(clp->rmcup, 1, cl_putchar); (void)fflush(stdout); } break; case SA_INVERSE: if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { if (clp->smso == NULL) return (1); if (on) (void)tputs(clp->smso, 1, cl_putchar); else (void)tputs(clp->rmso, 1, cl_putchar); (void)fflush(stdout); } else { if (on) (void)standout(); else (void)standend(); } break; default: (void)fflush(stdout); abort(); } (void)fflush(stdout); return (0); } /* * cl_baud -- * Return the baud rate. * * PUBLIC: int cl_baud(SCR *, unsigned long *); */ int cl_baud(SCR *sp, unsigned long *ratep) { CL_PRIVATE *clp; /* * XXX * There's no portable way to get a "baud rate" -- cfgetospeed(3) * returns the value associated with some #define, which we may * never have heard of, or which may be a purely local speed. Vi * only cares if it's SLOW (w300), slow (w1200) or fast (w9600). * Try and detect the slow ones, and default to fast. */ clp = CLP(sp); switch (cfgetospeed(&clp->orig)) { case B50: case B75: case B110: case B134: case B150: case B200: case B300: case B600: *ratep = 600; break; case B1200: *ratep = 1200; break; default: *ratep = 9600; break; } return (0); } /* * cl_bell -- * Ring the bell/flash the screen. * * PUBLIC: int cl_bell(SCR *); */ int cl_bell(SCR *sp) { if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) (void)!write(STDOUT_FILENO, "\07", 1); /* \a */ else { /* * If the screen has not been setup we cannot call * curses routines yet. */ if (F_ISSET(sp, SC_SCR_VI)) { /* * Vi has an edit option which determines if the * terminal should be beeped or the screen flashed. */ if (O_ISSET(sp, O_FLASH)) (void)flash(); else (void)beep(); } else if (!O_ISSET(sp, O_FLASH)) (void)!write(STDOUT_FILENO, "\07", 1); } return (0); } /* * cl_clrtoeol -- * Clear from the current cursor to the end of the line. * * PUBLIC: int cl_clrtoeol(SCR *); */ int cl_clrtoeol(SCR *sp) { return (clrtoeol() == ERR); } /* * cl_cursor -- * Return the current cursor position. * * PUBLIC: int cl_cursor(SCR *, size_t *, size_t *); */ int cl_cursor(SCR *sp, size_t *yp, size_t *xp) { /* * The curses screen support splits a single underlying curses screen * into multiple screens to support split screen semantics. For this * reason the returned value must be adjusted to be relative to the * current screen, and not absolute. Screens that implement the split * using physically distinct screens won't need this hack. */ getyx(stdscr, *yp, *xp); *yp -= sp->woff; return (0); } /* * cl_deleteln -- * Delete the current line, scrolling all lines below it. * * PUBLIC: int cl_deleteln(SCR *); */ int cl_deleteln(SCR *sp) { size_t oldy, oldx; /* * This clause is required because the curses screen uses reverse * video to delimit split screens. If the screen does not do this, * this code won't be necessary. * * If the bottom line was in reverse video, rewrite it in normal * video before it's scrolled. * * Check for the existence of a chgat function; XSI requires it, but * historic implementations of System V curses don't. If it's not * a #define, we'll fall back to doing it by hand, which is slow but * acceptable. * * By hand means walking through the line, retrieving and rewriting * each character. Curses has no EOL marker, so track strings of * spaces, and copy the trailing spaces only if there's a non-space * character following. */ if (!F_ISSET(sp, SC_SCR_EXWROTE) && IS_SPLIT(sp)) { getyx(stdscr, oldy, oldx); mvchgat(RLNO(sp, LASTLINE(sp)), 0, -1, A_NORMAL, 0, NULL); (void)move(oldy, oldx); } /* * The bottom line is expected to be blank after this operation, * and other screens must support that semantic. */ return (deleteln() == ERR); } /* * cl_ex_adjust -- * Adjust the screen for ex. This routine is purely for standalone * ex programs. All special purpose, all special case. * * PUBLIC: int cl_ex_adjust(SCR *, exadj_t); */ int cl_ex_adjust(SCR *sp, exadj_t action) { CL_PRIVATE *clp; int cnt; clp = CLP(sp); switch (action) { case EX_TERM_SCROLL: /* Move the cursor up one line if that's possible. */ if (clp->cuu1 != NULL) (void)tputs(clp->cuu1, 1, cl_putchar); else if (clp->cup != NULL) (void)tputs(tgoto(clp->cup, 0, LINES - 2), 1, cl_putchar); else return (0); /* FALLTHROUGH */ case EX_TERM_CE: /* Clear the line. */ if (clp->el != NULL) { (void)putchar('\r'); (void)tputs(clp->el, 1, cl_putchar); } else { /* * Historically, ex didn't erase the line, so, if the * displayed line was only a single glyph, and * was more than one glyph, the output would not fully * overwrite the user's input. To fix this, output * the maximum character number of spaces. Note, * this won't help if the user entered extra prompt * or characters before the command character. * We'd have to do a lot of work to make that work, and * it's almost certainly not worth the effort. */ for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) (void)putchar('\b'); for (cnt = 0; cnt < MAX_CHARACTER_COLUMNS; ++cnt) (void)putchar(' '); (void)putchar('\r'); (void)fflush(stdout); } break; default: abort(); } return (0); } /* * cl_imctrl -- * Control the state of input method by using escape sequences compatible * to Tera Term and RLogin. * * PUBLIC: void cl_imctrl __P((SCR *, imctrl_t)); */ void cl_imctrl(SCR *sp, imctrl_t action) { #define TT_IM_OFF "\033[orig.c_cc[VEOF]) == _POSIX_VDISABLE; break; case KEY_VERASE: *dnep = (*chp = clp->orig.c_cc[VERASE]) == _POSIX_VDISABLE; break; case KEY_VKILL: *dnep = (*chp = clp->orig.c_cc[VKILL]) == _POSIX_VDISABLE; break; #ifdef VWERASE case KEY_VWERASE: *dnep = (*chp = clp->orig.c_cc[VWERASE]) == _POSIX_VDISABLE; break; #endif /* ifdef VWERASE */ default: *dnep = 1; break; } return (0); } /* * cl_move -- * Move the cursor. * * PUBLIC: int cl_move(SCR *, size_t, size_t); */ int cl_move(SCR *sp, size_t lno, size_t cno) { /* See the comment in cl_cursor. */ if (move(RLNO(sp, lno), cno) == ERR) { msgq(sp, M_ERR, "Error: move: l(%u) c(%u) o(%u)", lno, cno, sp->woff); return (1); } return (0); } /* * cl_refresh -- * Refresh the screen. * * PUBLIC: int cl_refresh(SCR *, int); */ int cl_refresh(SCR *sp, int repaint) { CL_PRIVATE *clp; clp = CLP(sp); (void)clp; /* * If we received a killer signal, we're done, there's no point * in refreshing the screen. */ if (cl_sigterm) return (0); /* * If repaint is set, the editor is telling us that we don't know * what's on the screen, so we have to repaint from scratch. * * In the curses library, doing wrefresh(curscr) is okay, but the * screen flashes when we then apply the refresh() to bring it up * to date. So, use clearok(). */ if (repaint) clearok(curscr, 1); return (refresh() == ERR); } /* * cl_rename -- * Rename the file. * * PUBLIC: int cl_rename(SCR *, char *, int); */ int cl_rename(SCR *sp, char *name, int on) { GS *gp; CL_PRIVATE *clp; char *ttype; gp = sp->gp; clp = CLP(sp); ttype = OG_STR(gp, GO_TERM); /* * XXX * We can only rename windows for xterm. */ if (on) { if (F_ISSET(clp, CL_RENAME_OK) && !strncmp(ttype, "xterm", sizeof("xterm") - 1)) { F_SET(clp, CL_RENAME); (void)printf(XTERM_RENAME, name); (void)fflush(stdout); } } else if (F_ISSET(clp, CL_RENAME)) { F_CLR(clp, CL_RENAME); (void)printf(XTERM_RENAME, ttype); (void)fflush(stdout); } return (0); } /* * cl_suspend -- * Suspend a screen. * * PUBLIC: int cl_suspend(SCR *, int *); */ int cl_suspend(SCR *sp, int *allowedp) { struct termios t; CL_PRIVATE *clp; size_t oldy, oldx; int changed; clp = CLP(sp); *allowedp = 1; /* * The ex implementation of this function isn't needed by screens not * supporting ex commands that require full terminal canonical mode * (e.g. :suspend). * * The vi implementation of this function isn't needed by screens not * supporting vi process suspension, i.e. any screen that isn't backed * by a UNIX shell. * * Setting allowedp to 0 will cause the editor to reject the command. */ if (F_ISSET(sp, SC_EX)) { /* Save the terminal settings, and restore the original ones. */ if (F_ISSET(clp, CL_STDIN_TTY)) { (void)tcgetattr(STDIN_FILENO, &t); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->orig); } /* Stop the process group. */ (void)kill(0, SIGTSTP); /* Time passes ... */ /* Restore terminal settings. */ if (F_ISSET(clp, CL_STDIN_TTY)) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &t); return (0); } /* * Move to the lower left-hand corner of the screen. * * XXX * Not sure this is necessary in System V implementations, but it * shouldn't hurt. */ getyx(stdscr, oldy, oldx); (void)move(LINES - 1, 0); (void)refresh(); /* * Temporarily end the screen. System V introduced a semantic where * endwin() could be restarted. We use it because restarting curses * from scratch often fails in System V. */ /* Restore the cursor keys to normal mode. */ (void)keypad(stdscr, FALSE); /* Restore the window name. */ (void)cl_rename(sp, NULL, 0); (void)endwin(); /* * XXX * Restore the original terminal settings. This is bad -- the * reset can cause character loss from the tty queue. However, * we can't call endwin() in BSD curses implementations, and too * many System V curses implementations don't get it right. */ (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); /* Stop the process group. */ (void)kill(0, SIGTSTP); /* Time passes ... */ /* * If we received a killer signal, we're done. Leave everything * unchanged. In addition, the terminal has already been reset * correctly, so leave it alone. */ if (cl_sigterm) { F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); return (0); } /* Set the window name. */ (void)cl_rename(sp, sp->frp->name, 1); /* Put the cursor keys into application mode. */ (void)keypad(stdscr, TRUE); /* Refresh and repaint the screen. */ (void)move(oldy, oldx); (void)cl_refresh(sp, 1); /* If the screen changed size, set the SIGWINCH bit. */ if (cl_ssize(sp, 1, NULL, NULL, &changed)) return (1); if (changed) cl_sigwinch = 1; return (0); } /* * cl_usage -- * Print out the curses usage messages. * * PUBLIC: void cl_usage(void); */ void cl_usage() { switch (pmode) { case MODE_EX: (void)fprintf(stderr, "Usage: " #ifdef DEBUG "ex [ -FRrSsv ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\n"); #else "ex [ -FRrSsv ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\n"); #endif /* ifdef DEBUG */ break; case MODE_VI: (void)fprintf(stderr, "Usage: " #ifdef DEBUG "vi [ -eFRrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\n"); #else "vi [ -eFRrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\n"); #endif /* ifdef DEBUG */ break; case MODE_VIEW: (void)fprintf(stderr, "Usage: " #ifdef DEBUG "view [ -eFrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ -T tracefile ] [ file ... ]\n"); #else "view [ -eFrS ] [ -c cmd | -C cmd ] [ -t tag ] [ -w size ] [ file ... ]\n"); #endif /* ifdef DEBUG */ break; } } #ifdef DEBUG /* * gdbrefresh -- * Stub routine so can flush out curses screen changes using gdb. */ int gdbrefresh() { refresh(); return (0); /* XXX Convince gdb to run it. */ } #endif /* ifdef DEBUG */ ================================================ FILE: cl/cl_main.c ================================================ /* $OpenBSD: cl_main.c,v 1.36 2021/10/24 21:24:17 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #ifdef __solaris__ # define _XPG7 # undef __EXTENSIONS__ # define __EXTENSIONS__ # include #endif /* ifdef __solaris__ */ #include #include #include #include #include #include #include #include "errc.h" #ifdef __solaris__ # undef GS #endif /* ifdef __solaris__ */ #include "../common/common.h" #include "cl.h" #undef open void cl_imctrl (SCR *, imctrl_t); GS *__global_list; /* GLOBAL: List of screens. */ volatile sig_atomic_t cl_sigint; volatile sig_atomic_t cl_sigterm; volatile sig_atomic_t cl_sigwinch; static void cl_func_std(GS *); static CL_PRIVATE *cl_init(GS *); static GS *gs_init(void); static int setsig(int, struct sigaction *, void (*)(int)); static void sig_end(GS *); static void term_init(char *); /* * main -- * This is the main loop for the standalone curses editor. */ int main(int argc, char *argv[]) { CL_PRIVATE *clp; GS *gp; size_t rows, cols; int rval; char *ttype; /* Create and initialize the global structure. */ __global_list = gp = gs_init(); /* Create and initialize the CL_PRIVATE structure. */ clp = cl_init(gp); /* * Initialize the terminal information. * * We have to know what terminal it is from the start, since we may * have to use termcap/terminfo to find out how big the screen is. */ ttype = getenv("TERM"); if (ttype == NULL) ttype = "unknown"; term_init(ttype); ttype = getenv("TERM"); /* Add the terminal type to the global structure. */ if ((OG_D_STR(gp, GO_TERM) = OG_STR(gp, GO_TERM) = strdup(ttype)) == NULL) openbsd_err(1, NULL); /* Figure out how big the screen is. */ if (cl_ssize(NULL, 0, &rows, &cols, NULL)) exit (1); /* Add the rows and columns to the global structure. */ OG_VAL(gp, GO_LINES) = OG_D_VAL(gp, GO_LINES) = rows; OG_VAL(gp, GO_COLUMNS) = OG_D_VAL(gp, GO_COLUMNS) = cols; /* Ex wants stdout to be buffered. */ (void)setvbuf(stdout, NULL, _IOFBF, 0); /* Start catching signals. */ if (sig_init(gp, NULL)) exit (1); /* Run ex/vi. */ rval = editor(gp, argc, argv); /* Clean up signals. */ sig_end(gp); /* Clean up the terminal. */ (void)cl_quit(gp); /* * XXX * Reset the O_MESG option. */ if (clp->tgw != TGW_UNKNOWN) (void)cl_omesg(NULL, clp, clp->tgw == TGW_SET); /* * XXX * Reset the X11 xterm icon/window name. */ if (F_ISSET(clp, CL_RENAME)) { (void)printf(XTERM_RENAME, ttype); (void)fflush(stdout); } /* If a killer signal arrived, pretend we just got it. */ if (cl_sigterm) { (void)signal(cl_sigterm, SIG_DFL); (void)kill(getpid(), cl_sigterm); /* NOTREACHED */ } /* Free the global and CL private areas. */ #if defined(DEBUG) || defined(PURIFY) free(clp); free(gp); #endif /* if defined(DEBUG) || defined(PURIFY) */ exit (rval); } /* * gs_init -- * Create and partially initialize the GS structure. */ static GS * gs_init(void) { GS *gp; /* Allocate the global structure. */ if ((gp = calloc(1, sizeof(GS))) == NULL) openbsd_err(1, NULL); return (gp); } /* * cl_init -- * Create and partially initialize the CL structure. */ static CL_PRIVATE * cl_init(GS *gp) { CL_PRIVATE *clp; int fd; /* Allocate the CL private structure. */ if ((clp = calloc(1, sizeof(CL_PRIVATE))) == NULL) openbsd_err(1, NULL); gp->cl_private = clp; /* * Set the CL_STDIN_TTY flag. It's purpose is to avoid setting * and resetting the tty if the input isn't from there. We also * use the same test to determine if we're running a script or * not. */ if (isatty(STDIN_FILENO)) F_SET(clp, CL_STDIN_TTY); else F_SET(gp, G_SCRIPTED); /* * We expect that if we've lost our controlling terminal that the * open() (but not the tcgetattr()) will fail. */ if (F_ISSET(clp, CL_STDIN_TTY)) { if (tcgetattr(STDIN_FILENO, &clp->orig) == -1) goto tcfail; } else if ((fd = open(_PATH_TTY, O_RDONLY)) != -1) { if (tcgetattr(fd, &clp->orig) == -1) tcfail: openbsd_err(1, "tcgetattr"); (void)close(fd); } /* Initialize the list of curses functions. */ cl_func_std(gp); return (clp); } /* * term_init -- * Initialize terminal information. */ static void term_init(char *ttype) { int err; /* Set up the terminal database information. */ setupterm(ttype, STDOUT_FILENO, &err); if (err == 0) { if (strlen(ttype) == 0) ttype = "unknown"; (void)fprintf(stderr, "%s: Unknown terminal type '%s'; falling back to 'vt100'\n", bsd_getprogname(), ttype); sleep(5); setenv("TERM", "vt100", 1); setupterm("vt100", STDOUT_FILENO, &err); } switch (err) { case -1: openbsd_errx(1, "No terminal database found"); case 0: openbsd_errx(1, "%s: unknown terminal type", ttype); } } static void h_int(int signo) { cl_sigint = 1; } static void h_term(int signo) { cl_sigterm = signo; } static void h_winch(int signo) { cl_sigwinch = 1; } /* * sig_init -- * Initialize signals. * * PUBLIC: int sig_init(GS *, SCR *); */ int sig_init(GS *gp, SCR *sp) { CL_PRIVATE *clp; clp = GCLP(gp); cl_sigint = 0; cl_sigterm = 0; cl_sigwinch = 0; if (sp == NULL) { if (setsig(SIGHUP, &clp->oact[INDX_HUP], h_term) || setsig(SIGINT, &clp->oact[INDX_INT], h_int) || setsig(SIGQUIT, &clp->oact[INDX_INT], h_int) || setsig(SIGTERM, &clp->oact[INDX_TERM], h_term) || setsig(SIGWINCH, &clp->oact[INDX_WINCH], h_winch) ) openbsd_err(1, NULL); } else if (setsig(SIGHUP, NULL, h_term) || setsig(SIGINT, NULL, h_int) || setsig(SIGQUIT, NULL, h_int) || setsig(SIGTERM, NULL, h_term) || setsig(SIGWINCH, NULL, h_winch) ) { msgq(sp, M_SYSERR, "signal-reset"); } return (0); } /* * setsig -- * Set a signal handler. */ static int setsig(int signo, struct sigaction *oactp, void (*handler)(int)) { struct sigaction act; /* * Use sigaction(2), not signal(3), since we don't always want to * restart system calls. The example is when waiting for a command * mode keystroke and SIGWINCH arrives. Besides, you can't portably * restart system calls (thanks, POSIX!). */ act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; return (sigaction(signo, &act, oactp)); } /* * sig_end -- * End signal setup. */ static void sig_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); (void)sigaction(SIGHUP, NULL, &clp->oact[INDX_HUP]); (void)sigaction(SIGINT, NULL, &clp->oact[INDX_INT]); (void)sigaction(SIGQUIT, NULL, &clp->oact[INDX_INT]); (void)sigaction(SIGTERM, NULL, &clp->oact[INDX_TERM]); (void)sigaction(SIGWINCH, NULL, &clp->oact[INDX_WINCH]); } /* * cl_func_std -- * Initialize the standard curses functions. */ static void cl_func_std(GS *gp) { gp->scr_addstr = cl_addstr; gp->scr_attr = cl_attr; gp->scr_baud = cl_baud; gp->scr_bell = cl_bell; gp->scr_busy = NULL; gp->scr_clrtoeol = cl_clrtoeol; gp->scr_cursor = cl_cursor; gp->scr_deleteln = cl_deleteln; gp->scr_event = cl_event; gp->scr_ex_adjust = cl_ex_adjust; gp->scr_fmap = cl_fmap; gp->scr_imctrl = cl_imctrl; gp->scr_insertln = cl_insertln; gp->scr_keyval = cl_keyval; gp->scr_move = cl_move; gp->scr_msg = NULL; gp->scr_optchange = cl_optchange; gp->scr_refresh = cl_refresh; gp->scr_rename = cl_rename; gp->scr_screen = cl_screen; gp->scr_suspend = cl_suspend; gp->scr_usage = cl_usage; } ================================================ FILE: cl/cl_read.c ================================================ /* $OpenBSD: cl_read.c,v 1.23 2021/09/02 11:19:02 schwarze Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #ifdef __solaris__ # define _XPG7 # undef __EXTENSIONS__ # define __EXTENSIONS__ # include #endif /* ifdef __solaris__ */ #include #include #include #include #include #if defined(__solaris__) # define __EXTENSIONS__ # include # include #endif /* if defined(__solaris__) */ #include #include #ifdef __solaris__ # undef GS #endif /* ifdef __solaris__ */ #include "../common/common.h" #include "../ex/script.h" #include "cl.h" #undef open #if defined(_AIX) || defined(__illumos__) # undef lines # undef columns #endif /* if defined(_AIX) || defined(__illumos__) */ static input_t cl_read(SCR *, u_int32_t, CHAR_T *, size_t, int *, struct timeval *); static int cl_resize(SCR *sp, size_t lines, size_t columns); /* * cl_event -- * Return a single event. * * PUBLIC: int cl_event(SCR *, EVENT *, u_int32_t, int); */ int cl_event(SCR *sp, EVENT *evp, u_int32_t flags, int ms) { struct timeval t, *tp; CL_PRIVATE *clp; size_t lines, columns; int changed, nr; /* * Queue signal based events. We never clear SIGHUP or SIGTERM events, * so that we just keep returning them until the editor dies. */ clp = CLP(sp); retest: if (LF_ISSET(EC_INTERRUPT) || cl_sigint) { if (cl_sigint) { cl_sigint = 0; evp->e_event = E_INTERRUPT; } else evp->e_event = E_TIMEOUT; return (0); } switch (cl_sigterm) { case SIGHUP: evp->e_event = E_SIGHUP; return (0); case SIGTERM: evp->e_event = E_SIGTERM; return (0); default: break; } if (cl_sigwinch) { cl_sigwinch = 0; if (cl_ssize(sp, 1, &lines, &columns, &changed)) return (1); if (changed) { (void)cl_resize(sp, lines, columns); evp->e_event = E_WRESIZE; return (0); } /* No real change, ignore the signal. */ } /* Set timer. */ if (ms == 0) tp = NULL; else { t.tv_sec = ms / 1000; t.tv_usec = (ms % 1000) * 1000; tp = &t; } /* Read input characters. */ switch (cl_read(sp, LF_ISSET(EC_QUOTED | EC_RAW), clp->ibuf, sizeof(clp->ibuf), &nr, tp)) { case INP_OK: evp->e_csp = clp->ibuf; evp->e_len = nr; evp->e_event = E_STRING; break; case INP_EOF: evp->e_event = E_EOF; break; case INP_ERR: evp->e_event = E_ERR; break; case INP_INTR: goto retest; case INP_TIMEOUT: evp->e_event = E_TIMEOUT; break; default: abort(); } return (0); } /* * cl_read -- * Read characters from the input. */ static input_t cl_read(SCR *sp, u_int32_t flags, CHAR_T *bp, size_t blen, int *nrp, struct timeval *tp) { struct termios term1, term2; CL_PRIVATE *clp; GS *gp; struct pollfd pfd[1]; input_t rval; int nr, term_reset, timeout; gp = sp->gp; clp = CLP(sp); term_reset = 0; /* * 1: A read from a file or a pipe. In this case, the reads * never timeout regardless. This means that we can hang * when trying to complete a map, but we're going to hang * on the next read anyway. */ if (!F_ISSET(clp, CL_STDIN_TTY)) { switch (nr = read(STDIN_FILENO, bp, blen)) { case 0: return (INP_EOF); case -1: goto err; default: *nrp = nr; return (INP_OK); } /* NOTREACHED */ } /* * 2: A read with an associated timeout, e.g., trying to complete * a map sequence. If input exists, we fall into #3. */ tty_retry: if (tp != NULL) { pfd[0].fd = STDIN_FILENO; pfd[0].events = POLLIN; timeout = tp ? (tp->tv_sec * 1000) + (tp->tv_usec / 1000) : 0; switch (poll(pfd, 1, timeout)) { case 0: return (INP_TIMEOUT); case -1: goto err; default: break; } } /* * The user can enter a key in the editor to quote a character. If we * get here and the next key is supposed to be quoted, do what we can. * Reset the tty so that the user can enter a ^C, ^Q, ^S. There's an * obvious race here, when the key has already been entered, but there's * nothing that we can do to fix that problem. * * The editor can ask for the next literal character even thought it's * generally running in line-at-a-time mode. Do what we can. */ if (LF_ISSET(EC_QUOTED | EC_RAW) && !tcgetattr(STDIN_FILENO, &term1)) { term_reset = 1; if (LF_ISSET(EC_QUOTED)) { term2 = term1; term2.c_lflag &= ~ISIG; term2.c_iflag &= ~(IXON | IXOFF); (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term2); } else (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter); } /* * 3: Wait for input. * * Select on the command input and scripting window file descriptors. * It's ugly that we wait on scripting file descriptors here, but it's * the only way to keep from locking out scripting windows. */ if (F_ISSET(gp, G_SCRWIN)) { if (sscr_check_input(sp)) goto err; } /* * 4: Read the input. * * !!! * What's going on here is some scary stuff. Ex runs the terminal in * canonical mode. So, the character terminating a line of * input is returned in the buffer, but a trailing character is * not similarly included. As ex uses 0 and ^ as autoindent * commands, it has to see the trailing characters to determine * the difference between the user entering "0ab" and "0ab". We * leave an extra slot in the buffer, so that we can add a trailing * character if the buffer isn't terminated by a . We * lose if the buffer is too small for the line and exactly N characters * are entered followed by an character. */ #define ONE_FOR_EOF 1 switch (nr = read(STDIN_FILENO, bp, blen - ONE_FOR_EOF)) { case 0: /* EOF. */ /* * ^D in canonical mode returns a read of 0, i.e. EOF. EOF is * a valid command, but we don't want to loop forever because * the terminal driver is returning EOF because the user has * disconnected. The editor will almost certainly try to write * something before this fires, which should kill us, but You * Never Know. */ if (++clp->eof_count < 50) { bp[0] = clp->orig.c_cc[VEOF]; *nrp = 1; rval = INP_OK; } else rval = INP_EOF; break; case -1: /* Error or interrupt. */ err: if (errno == EINTR) rval = INP_INTR; else if (errno == EAGAIN) goto tty_retry; else { rval = INP_ERR; msgq(sp, M_SYSERR, "input"); } break; default: /* Input characters. */ if (F_ISSET(sp, SC_EX) && bp[nr - 1] != '\n') bp[nr++] = clp->orig.c_cc[VEOF]; *nrp = nr; clp->eof_count = 0; rval = INP_OK; break; } /* Restore the terminal state if it was modified. */ if (term_reset) (void)tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &term1); return (rval); } /* * cl_resize -- * Reset the options for a resize event. */ static int cl_resize(SCR *sp, size_t lines, size_t columns) { ARGS *argv[2], a, b; char b1[1024]; a.bp = b1; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; (void)snprintf(b1, sizeof(b1), "lines=%lu", (unsigned long)lines); a.len = strlen(b1); if (opts_set(sp, argv, NULL)) return (1); (void)snprintf(b1, sizeof(b1), "columns=%lu", (unsigned long)columns); a.len = strlen(b1); if (opts_set(sp, argv, NULL)) return (1); return (0); } ================================================ FILE: cl/cl_screen.c ================================================ /* $OpenBSD: cl_screen.c,v 1.28 2017/04/18 01:45:33 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #if defined(__solaris__) # define __EXTENSIONS__ # include # include #endif /* if defined(__solaris__) */ #include #include #include #include #include "../common/common.h" #include "cl.h" static int cl_ex_end(GS *); static int cl_ex_init(SCR *); static void cl_freecap(CL_PRIVATE *); static int cl_vi_end(GS *); static int cl_vi_init(SCR *); static int cl_putenv(char *, char *, unsigned long); /* * cl_screen -- * Switch screen types. * * PUBLIC: int cl_screen(SCR *, u_int32_t); */ int cl_screen(SCR *sp, u_int32_t flags) { CL_PRIVATE *clp; GS *gp; gp = sp->gp; clp = CLP(sp); /* See if the current information is incorrect. */ if (F_ISSET(gp, G_SRESTART)) { if ((!F_ISSET(sp, SC_SCR_EX | SC_SCR_VI) || resizeterm(O_VAL(sp, O_LINES), O_VAL(sp, O_COLUMNS))) && cl_quit(gp)) return (1); F_CLR(gp, G_SRESTART); } /* See if we're already in the right mode. */ if ((LF_ISSET(SC_EX) && F_ISSET(sp, SC_SCR_EX)) || (LF_ISSET(SC_VI) && F_ISSET(sp, SC_SCR_VI))) return (0); /* * Fake leaving ex mode. * * We don't actually exit ex or vi mode unless forced (e.g. by a window * size change). This is because many curses implementations can't be * called twice in a single program. Plus, it's faster. If the editor * "leaves" vi to enter ex, when it exits ex we'll just fall back into * vi. */ if (F_ISSET(sp, SC_SCR_EX)) F_CLR(sp, SC_SCR_EX); /* * Fake leaving vi mode. * * Clear out the rest of the screen if we're in the middle of a split * screen. Move to the last line in the current screen -- this makes * terminal scrolling happen naturally. Note: *don't* move past the * end of the screen, as there are ex commands (e.g., :read ! cat file) * that don't want to. Don't clear the info line, its contents may be * valid, e.g. :file|append. */ if (F_ISSET(sp, SC_SCR_VI)) { F_CLR(sp, SC_SCR_VI); if (TAILQ_NEXT(sp, q)) { (void)move(RLNO(sp, sp->rows), 0); clrtobot(); } (void)move(RLNO(sp, sp->rows) - 1, 0); refresh(); } /* Enter the requested mode. */ if (LF_ISSET(SC_EX)) { if (cl_ex_init(sp)) return (1); F_SET(clp, CL_IN_EX | CL_SCR_EX_INIT); /* * If doing an ex screen for ex mode, move to the last line * on the screen. */ if (F_ISSET(sp, SC_EX) && clp->cup != NULL) tputs(tgoto(clp->cup, 0, O_VAL(sp, O_LINES) - 1), 1, cl_putchar); } else { if (cl_vi_init(sp)) return (1); F_CLR(clp, CL_IN_EX); F_SET(clp, CL_SCR_VI_INIT); } return (0); } /* * cl_quit -- * Shutdown the screens. * * PUBLIC: int cl_quit(GS *); */ int cl_quit(GS *gp) { CL_PRIVATE *clp; int rval; rval = 0; clp = GCLP(gp); /* * If we weren't really running, ignore it. This happens if the * screen changes size before we've called curses. */ if (!F_ISSET(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT)) return (0); /* Clean up the terminal mappings. */ if (cl_term_end(gp)) rval = 1; /* Really leave vi mode. */ if (F_ISSET(clp, CL_STDIN_TTY) && F_ISSET(clp, CL_SCR_VI_INIT) && cl_vi_end(gp)) rval = 1; /* Really leave ex mode. */ if (F_ISSET(clp, CL_STDIN_TTY) && F_ISSET(clp, CL_SCR_EX_INIT) && cl_ex_end(gp)) rval = 1; /* * If we were running ex when we quit, or we're using an implementation * of curses where endwin() doesn't get this right, restore the original * terminal modes. * * XXX * We always do this because it's too hard to figure out what curses * implementations get it wrong. It may discard type-ahead characters * from the tty queue. */ if (F_ISSET(clp, CL_STDIN_TTY)) (void)tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->orig); F_CLR(clp, CL_SCR_EX_INIT | CL_SCR_VI_INIT); return (rval); } /* * cl_vi_init -- * Initialize the curses vi screen. */ static int cl_vi_init(SCR *sp) { CL_PRIVATE *clp; char *o_cols, *o_lines, *o_term, *ttype; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_VI_INIT)) goto fast; /* Curses vi always reads from (and writes to) a terminal. */ if (!F_ISSET(clp, CL_STDIN_TTY) || !isatty(STDOUT_FILENO)) { msgq(sp, M_ERR, "Standard input and standard output must be a terminal"); return (1); } /* We'll need a terminal type. */ if (opts_empty(sp, O_TERM, 0)) return (1); ttype = O_STR(sp, O_TERM); /* * XXX * Changing the row/column and terminal values is done by putting them * into the environment, which is then read by curses. What this loses * in ugliness, it makes up for in stupidity. We can't simply put the * values into the environment ourselves, because in the presence of a * kernel mechanism for returning the window size, entering values into * the environment will screw up future screen resizing events, e.g. if * the user enters a :shell command and then resizes their window. So, * if they weren't already in the environment, we make sure to delete * them immediately after setting them. * * XXX * Putting the TERM variable into the environment is necessary, even * though we're using newterm() here. We may be using initscr() as * the underlying function. */ o_term = getenv("TERM"); cl_putenv("TERM", ttype, 0); o_lines = getenv("LINES"); cl_putenv("LINES", NULL, (unsigned long)O_VAL(sp, O_LINES)); o_cols = getenv("COLUMNS"); cl_putenv("COLUMNS", NULL, (unsigned long)O_VAL(sp, O_COLUMNS)); /* * The terminal is always initialized, either in `main`, or by a * previous call to newterm(3). */ (void)del_curterm(cur_term); /* * We don't care about the SCREEN reference returned by newterm, we * never have more than one SCREEN at a time. */ errno = 0; if (newterm(ttype, stdout, stdin) == NULL) { if (errno) msgq(sp, M_SYSERR, "%s", ttype); else msgq(sp, M_ERR, "%s: unknown terminal type", ttype); return (1); } if (o_term == NULL) unsetenv("TERM"); if (o_lines == NULL) unsetenv("LINES"); if (o_cols == NULL) unsetenv("COLUMNS"); /* * XXX * Someone got let out alone without adult supervision -- the SunOS * newterm resets the signal handlers. There's a race, but it's not * worth closing. */ (void)sig_init(sp->gp, sp); /* * We use raw mode. What we want is 8-bit clean, however, signals * and flow control should continue to work. Admittedly, it sounds * like cbreak, but it isn't. Using cbreak() can get you additional * things like IEXTEN, which turns on flags like DISCARD and LNEXT. * * !!! * If raw isn't turning off echo and newlines, something's wrong. * However, it shouldn't hurt. */ noecho(); /* No character echo. */ nonl(); /* No CR/NL translation. */ raw(); /* 8-bit clean. */ idlok(stdscr, 1); /* Use hardware insert/delete line. */ /* Put the cursor keys into application mode. */ (void)keypad(stdscr, TRUE); /* * XXX * The screen TI sequence just got sent. See the comment in * cl_funcs.c:cl_attr(). */ clp->ti_te = TI_SENT; /* * XXX * Historic implementations of curses handled SIGTSTP signals * in one of three ways. They either: * * 1: Set their own handler, regardless. * 2: Did not set a handler if a handler was already installed. * 3: Set their own handler, but then called any previously set * handler after completing their own cleanup. * * We don't try and figure out which behavior is in place, we force * it to SIG_DFL after initializing the curses interface, which means * that curses isn't going to take the signal. Since curses isn't * reentrant (i.e., the whole curses SIGTSTP interface is a fantasy), * we're doing The Right Thing. */ (void)signal(SIGTSTP, SIG_DFL); /* * If flow control was on, turn it back on. Turn signals on. ISIG * turns on VINTR, VQUIT, VDSUSP and VSUSP. The main curses code * already installed a handler for VINTR. We're going to disable the * other three. * * XXX * We want to use ^Y as a vi scrolling command. If the user has the * DSUSP character set to ^Y (common practice) clean it up. As it's * equally possible that the user has VDSUSP set to 'a', we disable * it regardless. It doesn't make much sense to suspend vi at read, * so I don't think anyone will care. Alternatively, we could look * it up in the table of legal command characters and turn it off if * it matches one. * * XXX * We don't check to see if the user had signals enabled originally. * If they didn't, it's unclear what we're supposed to do here, but * it's also pretty unlikely. */ if (tcgetattr(STDIN_FILENO, &clp->vi_enter)) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (clp->orig.c_iflag & IXON) clp->vi_enter.c_iflag |= IXON; if (clp->orig.c_iflag & IXOFF) clp->vi_enter.c_iflag |= IXOFF; clp->vi_enter.c_lflag |= ISIG; #ifdef VDSUSP clp->vi_enter.c_cc[VDSUSP] = _POSIX_VDISABLE; #endif /* ifdef VDSUSP */ clp->vi_enter.c_cc[VQUIT] = _POSIX_VDISABLE; clp->vi_enter.c_cc[VSUSP] = _POSIX_VDISABLE; /* * XXX * OSF/1 doesn't turn off the , or * characters when curses switches into raw mode. It should be OK * to do it explicitly for everyone. */ #ifdef VDISCARD clp->vi_enter.c_cc[VDISCARD] = _POSIX_VDISABLE; #endif /* ifdef VDISCARD */ #ifdef VLNEXT clp->vi_enter.c_cc[VLNEXT] = _POSIX_VDISABLE; #endif /* ifdef VLNEXT */ #ifdef VSTATUS clp->vi_enter.c_cc[VSTATUS] = _POSIX_VDISABLE; #endif /* ifdef VSTATUS */ /* Initialize terminal based information. */ if (cl_term_init(sp)) goto err; fast: /* Set the terminal modes. */ if (tcsetattr(STDIN_FILENO, TCSASOFT | TCSADRAIN, &clp->vi_enter)) { if (errno == EINTR) goto fast; msgq(sp, M_SYSERR, "tcsetattr"); err: (void)cl_vi_end(sp->gp); return (1); } return (0); } /* * cl_vi_end -- * Shutdown the vi screen. */ static int cl_vi_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); /* Restore the cursor keys to normal mode. */ (void)keypad(stdscr, FALSE); /* * If we were running vi when we quit, scroll the screen up a single * line so we don't lose any information. * * Move to the bottom of the window (some endwin implementations don't * do this for you). */ if (!F_ISSET(clp, CL_IN_EX)) { (void)move(0, 0); (void)deleteln(); (void)move(LINES - 1, 0); (void)refresh(); } cl_freecap(clp); /* End curses window. */ (void)endwin(); /* Free the SCREEN created by newterm(3). */ #if defined(NCURSES_VERSION) delscreen(set_term(NULL)); #endif /* if defined(NCURSES_VERSION) */ /* * XXX * The screen TE sequence just got sent. See the comment in * cl_funcs.c:cl_attr(). */ clp->ti_te = TE_SENT; return (0); } /* * cl_ex_init -- * Initialize the ex screen. */ static int cl_ex_init(SCR *sp) { CL_PRIVATE *clp; clp = CLP(sp); /* If already initialized, just set the terminal modes. */ if (F_ISSET(clp, CL_SCR_EX_INIT)) goto fast; /* If not reading from a file, we're done. */ if (!F_ISSET(clp, CL_STDIN_TTY)) return (0); /* Get the ex termcap/terminfo strings. */ (void)cl_getcap(sp, "cup", &clp->cup); (void)cl_getcap(sp, "smso", &clp->smso); (void)cl_getcap(sp, "rmso", &clp->rmso); (void)cl_getcap(sp, "el", &clp->el); (void)cl_getcap(sp, "cuu1", &clp->cuu1); /* Enter_standout_mode and exit_standout_mode are paired. */ if (clp->smso == NULL || clp->rmso == NULL) { free(clp->smso); clp->smso = NULL; free(clp->rmso); clp->rmso = NULL; } /* * Turn on canonical mode, with normal input and output processing. * Start with the original terminal settings as the user probably * had them (including any local extensions) set correctly for the * current terminal. * * !!! * We can't get everything that we need portably; for example, ONLCR, * mapping to on output isn't required * by POSIX 1003.1b-1993. If this turns out to be a problem, then * we'll either have to play some games on the mapping, or we'll have * to make all ex printf's output \r\n instead of \n. */ clp->ex_enter = clp->orig; clp->ex_enter.c_lflag |= ECHO #if !defined(__managarm__) | ECHOCTL #endif | ECHOE | ECHOK #if !defined(__managarm__) | ECHOKE #endif | ICANON | IEXTEN | ISIG; clp->ex_enter.c_iflag |= ICRNL; clp->ex_enter.c_oflag |= ONLCR | OPOST; fast: if (tcsetattr(STDIN_FILENO, TCSADRAIN | TCSASOFT, &clp->ex_enter)) { if (errno == EINTR) goto fast; msgq(sp, M_SYSERR, "tcsetattr"); return (1); } return (0); } /* * cl_ex_end -- * Shutdown the ex screen. */ static int cl_ex_end(GS *gp) { CL_PRIVATE *clp; clp = GCLP(gp); cl_freecap(clp); return (0); } /* * cl_getcap -- * Retrieve termcap/terminfo strings. * * PUBLIC: int cl_getcap(SCR *, char *, char **); */ int cl_getcap(SCR *sp, char *name, char **elementp) { size_t len; char *t; if ((t = tigetstr(name)) != NULL && t != (char *)-1 && (len = strlen(t)) != 0) { MALLOC_RET(sp, *elementp, len + 1); memmove(*elementp, t, len + 1); } return (0); } /* * cl_freecap -- * Free any allocated termcap/terminfo strings. */ static void cl_freecap(CL_PRIVATE *clp) { free(clp->el); clp->el = NULL; free(clp->cup); clp->cup = NULL; free(clp->cuu1); clp->cuu1 = NULL; free(clp->rmso); clp->rmso = NULL; free(clp->smso); clp->smso = NULL; } /* * cl_putenv -- * Put a value into the environment. */ static int cl_putenv(char *name, char *str, unsigned long value) { char buf[40]; if (str == NULL) { (void)snprintf(buf, sizeof(buf), "%lu", value); return (setenv(name, buf, 1)); } else return (setenv(name, str, 1)); } ================================================ FILE: cl/cl_term.c ================================================ /* $OpenBSD: cl_term.c,v 1.29 2022/04/22 15:50:07 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #if defined(__solaris__) # define __EXTENSIONS__ # include # include #endif /* if defined(__solaris__) */ #include #include #ifdef __NetBSD__ # include #endif /* ifdef __NetBSD__ */ #include #include "../common/common.h" #include "cl.h" static int cl_pfmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); /* * XXX * THIS REQUIRES THAT ALL SCREENS SHARE A TERMINAL TYPE. */ typedef struct _tklist { char *ts; /* Key's termcap string. */ char *output; /* Corresponding vi command. */ char *name; /* Name. */ } TKLIST; static TKLIST const c_tklist[] = { /* Command mappings. */ {"kil1", "O", "insert line"}, {"kdch1", "x", "delete character"}, {"kcud1", "j", "cursor down"}, {"kel", "D", "delete to eol"}, {"kind", "\004", "scroll down"}, /* ^D */ {"kll", "$", "go to eol"}, {"khome", "^", "go to sol"}, {"kich1", "i", "insert at cursor"}, {"kdl1", "dd", "delete line"}, {"kcub1", "h", "cursor left"}, {"knp", "\006", "page down"}, /* ^F */ {"kpp", "\002", "page up"}, /* ^B */ {"kri", "\025", "scroll up"}, /* ^U */ {"ked", "dG", "delete to end of screen"}, {"kcuf1", "l", "cursor right"}, {"kcuu1", "k", "cursor up"}, {NULL, NULL, NULL}, }; static TKLIST const m1_tklist[] = { /* Input mappings (set or delete). */ {"kcud1", "\033ja", "cursor down"}, /* ^[ja */ {"kcub1", "\033ha", "cursor left"}, /* ^[ha */ {"kcuu1", "\033ka", "cursor up"}, /* ^[ka */ {"kcuf1", "\033la", "cursor right"}, /* ^[la */ {NULL, NULL, NULL}, }; /* * cl_term_init -- * Initialize the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_init(SCR *); */ int cl_term_init(SCR *sp) { SEQ *qp; TKLIST const *tkp; size_t output_len; char *t; /* Command mappings. */ for (tkp = c_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; output_len = 0; if (tkp->output != NULL) output_len = strlen(tkp->output); if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t), tkp->output, output_len, SEQ_COMMAND, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* Input mappings that are already set or are text deletions. */ for (tkp = m1_tklist; tkp->name != NULL; ++tkp) { if ((t = tigetstr(tkp->ts)) == NULL || t == (char *)-1) continue; /* * !!! * Some terminals' keys send single * characters. This is okay in command mapping, but not okay * in input mapping. That combination is the only one we'll * ever see, hopefully, so kluge it here for now. */ if (!strcmp(t, "\b")) continue; output_len = 0; if (tkp->output != NULL) output_len = strlen(tkp->output); if (seq_set(sp, tkp->name, strlen(tkp->name), t, strlen(t), tkp->output, output_len, SEQ_INPUT, SEQ_NOOVERWRITE | SEQ_SCREEN)) return (1); } /* * Rework any function key mappings that were set before the * screen was initialized. */ LIST_FOREACH(qp, & sp->gp->seqq, q) if (F_ISSET(qp, SEQ_FUNCMAP)) (void)cl_pfmap(sp, qp->stype, qp->input, qp->ilen, qp->output, qp->olen); return (0); } /* * cl_term_end -- * End the special keys defined by the termcap/terminfo entry. * * PUBLIC: int cl_term_end(GS *); */ int cl_term_end(GS *gp) { SEQ *qp, *nqp; /* Delete screen specific mappings. */ for (qp = LIST_FIRST(&gp->seqq); qp != NULL; qp = nqp) { nqp = LIST_NEXT(qp, q); if (F_ISSET(qp, SEQ_SCREEN)) (void)seq_mdel(qp); } return (0); } /* * cl_fmap -- * Map a function key. * * PUBLIC: int cl_fmap(SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); */ int cl_fmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { /* Ignore until the screen is running, do the real work then. */ if (F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_SCR_VI)) return (0); if (F_ISSET(sp, SC_EX) && !F_ISSET(sp, SC_SCR_EX)) return (0); return (cl_pfmap(sp, stype, from, flen, to, tlen)); } /* * cl_pfmap -- * Map a function key (private version). */ static int cl_pfmap(SCR *sp, seq_t stype, CHAR_T *from, size_t flen, CHAR_T *to, size_t tlen) { size_t nlen; char *p, key_name[64]; (void)snprintf(key_name, sizeof(key_name), "kf%d", atoi(from + 1)); if ((p = tigetstr(key_name)) == NULL || p == (char *)-1 || strlen(p) == 0) p = NULL; if (p == NULL) { msgq_str(sp, M_ERR, from, "This terminal has no %s key"); return (1); } nlen = snprintf(key_name, sizeof(key_name), "function key %d", atoi(from + 1)); if (nlen >= sizeof(key_name)) nlen = sizeof(key_name) - 1; return (seq_set(sp, key_name, nlen, p, strlen(p), to, tlen, stype, SEQ_NOOVERWRITE | SEQ_SCREEN)); } /* * cl_optchange -- * Curses screen specific "option changed" routine. * * PUBLIC: int cl_optchange(SCR *, int, char *, unsigned long *); */ int cl_optchange(SCR *sp, int opt, char *str, unsigned long *valp) { CL_PRIVATE *clp; clp = CLP(sp); switch (opt) { case O_TERM: F_CLR(sp, SC_SCR_EX | SC_SCR_VI); /* FALLTHROUGH */ case O_COLUMNS: case O_LINES: /* * Changing the terminal type requires that we reinitialize * curses, while resizing does not. */ F_SET(sp->gp, G_SRESTART); break; case O_MESG: (void)cl_omesg(sp, clp, !*valp); break; case O_WINDOWNAME: if (*valp) { F_CLR(clp, CL_RENAME_OK); (void)cl_rename(sp, NULL, 0); } else { F_SET(clp, CL_RENAME_OK); /* * If the screen is live, i.e. we're not reading the * .exrc file, update the window. */ if (sp->frp != NULL && sp->frp->name != NULL) (void)cl_rename(sp, sp->frp->name, 1); } break; } return (0); } /* * cl_omesg -- * Turn the tty write permission on or off. * * PUBLIC: int cl_omesg(SCR *, CL_PRIVATE *, int); */ int cl_omesg(SCR *sp, CL_PRIVATE *clp, int on) { struct stat sb; char *tty; /* Find the tty, get the current permissions. */ if ((tty = ttyname(STDERR_FILENO)) == NULL) { if (sp != NULL && isatty(STDERR_FILENO)) msgq(sp, M_SYSERR, "stderr"); return (1); } if (stat(tty, &sb) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "%s", tty); return (1); } sb.st_mode &= ACCESSPERMS; /* Save the original status if it's unknown. */ if (clp->tgw == TGW_UNKNOWN) clp->tgw = sb.st_mode & S_IWGRP ? TGW_SET : TGW_UNSET; /* Toggle the permissions. */ if (on) { if (chmod(tty, sb.st_mode | S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "messages not turned on: %s", tty); return (1); } } else if (chmod(tty, sb.st_mode & ~S_IWGRP) < 0) { if (sp != NULL) msgq(sp, M_SYSERR, "messages not turned off: %s", tty); return (1); } return (0); } /* * cl_ssize -- * Return the terminal size. * * PUBLIC: int cl_ssize(SCR *, int, size_t *, size_t *, int *); */ int cl_ssize(SCR *sp, int sigwinch, size_t *rowp, size_t *colp, int *changedp) { struct winsize win; size_t col, row; int rval; char *p; /* Assume it's changed. */ if (changedp != NULL) *changedp = 1; /* * !!! * sp may be NULL. * * Get the screen rows and columns. If the values are wrong, it's * not a big deal -- as soon as the user sets them explicitly the * environment will be set and the screen package will use the new * values. * * Try TIOCGWINSZ. */ row = col = 0; if (ioctl(STDERR_FILENO, TIOCGWINSZ, &win) != -1) { row = win.ws_row; col = win.ws_col; } /* If here because of suspend or a signal, only trust TIOCGWINSZ. */ if (sigwinch) { /* * Somebody didn't get TIOCGWINSZ right, or has suspend * without window resizing support. The user just lost, * but there's nothing we can do. */ if (row == 0 || col == 0) { if (changedp != NULL) *changedp = 0; return (0); } /* * SunOS systems deliver SIGWINCH when windows are uncovered * as well as when they change size. In addition, we call * here when continuing after being suspended since the window * may have changed size. Since we don't want to background * all of the screens just because the window was uncovered, * ignore the signal if there's no change. */ if (sp != NULL && row == O_VAL(sp, O_LINES) && col == O_VAL(sp, O_COLUMNS)) { if (changedp != NULL) *changedp = 0; return (0); } if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); } /* * !!! * If TIOCGWINSZ failed, or had entries of 0, try termcap. This * routine is called before any termcap or terminal information * has been set up. If there's no TERM environmental variable set, * let it go, at least ex can run. */ if (row == 0 || col == 0) { p = getenv("TERM"); if (p == NULL) goto noterm; if (row == 0) { if ((rval = tigetnum("lines")) < 0) msgq(sp, M_SYSERR, "tigetnum: lines"); else row = rval; } if (col == 0) { if ((rval = tigetnum("cols")) < 0) msgq(sp, M_SYSERR, "tigetnum: cols"); else col = rval; } } /* If nothing else, well, it's probably a VT100. */ noterm: if (row == 0) row = 24; if (col == 0) col = 80; /* * !!! * POSIX 1003.2 requires the environment to override everything. * Often, people can get nvi to stop messing up their screen by * deleting the LINES and COLUMNS environment variables from their * dot-files. */ if ((p = getenv("LINES")) != NULL && (rval = strtonum(p, 1, INT_MAX, NULL)) > 0) row = rval; if ((p = getenv("COLUMNS")) != NULL && (rval = strtonum(p, 1, INT_MAX, NULL)) > 0) col = rval; if (rowp != NULL) *rowp = row; if (colp != NULL) *colp = col; return (0); } /* * cl_putchar -- * Function version of putchar, for tputs. * * PUBLIC: int cl_putchar(int); */ int cl_putchar(int ch) { return (putchar(ch)); } ================================================ FILE: cl/extern.h ================================================ /* $OpenBSD: extern.h,v 1.8 2015/08/27 04:37:09 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)extern.h 8.10 (Berkeley) 7/20/94 */ #include "hash.h" int __bt_close(DB *); int __bt_cmp(BTREE *, const DBT *, EPG *); int __bt_defcmp(const DBT *, const DBT *); size_t __bt_defpfx(const DBT *, const DBT *); int __bt_delete(const DB *, const DBT *, unsigned int); int __bt_dleaf(BTREE *, const DBT *, PAGE *, unsigned int); int __bt_fd(const DB *); int __bt_free(BTREE *, PAGE *); int __bt_get(const DB *, const DBT *, DBT *, unsigned int); PAGE *__bt_new(BTREE *, pgno_t *); void __bt_pgin(void *, pgno_t, void *); void __bt_pgout(void *, pgno_t, void *); int __bt_put(const DB *dbp, DBT *, const DBT *, unsigned int); int __bt_ret(BTREE *, EPG *, DBT *, DBT *, DBT *, DBT *, int); EPG *__bt_search(BTREE *, const DBT *, int *); int __bt_seq(const DB *, DBT *, DBT *, unsigned int); void __bt_setcur(BTREE *, pgno_t, unsigned int); int __bt_split(BTREE *, PAGE *, const DBT *, const DBT *, int, size_t, u_int32_t); int __bt_sync(const DB *, unsigned int); int __ovfl_delete(BTREE *, void *); int __ovfl_get(BTREE *, void *, size_t *, void **, size_t *); int __ovfl_put(BTREE *, const DBT *, pgno_t *); int __rec_close(DB *); int __rec_delete(const DB *, const DBT *, unsigned int); int __rec_dleaf(BTREE *, PAGE *, u_int32_t); int __rec_fd(const DB *); int __rec_fmap(BTREE *, recno_t); int __rec_fout(BTREE *); int __rec_fpipe(BTREE *, recno_t); int __rec_get(const DB *, const DBT *, DBT *, unsigned int); int __rec_iput(BTREE *, recno_t, const DBT *, unsigned int); int __rec_put(const DB *dbp, DBT *, const DBT *, unsigned int); int __rec_ret(BTREE *, EPG *, recno_t, DBT *, DBT *); int __rec_seq(const DB *, DBT *, DBT *, unsigned int); int __rec_sync(const DB *, unsigned int); int __rec_vmap(BTREE *, recno_t); int __rec_vout(BTREE *); int __rec_vpipe(BTREE *, recno_t); BUFHEAD *__add_ovflpage(HTAB *, BUFHEAD *); int __addel(HTAB *, BUFHEAD *, const DBT *, const DBT *); int __big_delete(HTAB *, BUFHEAD *); int __big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *); int __big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int); int __big_return(HTAB *, BUFHEAD *, int, DBT *, int); int __big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *, int, u_int32_t, SPLIT_RETURN *); int __buf_free(HTAB *, int, int); void __buf_init(HTAB *, int); u_int32_t __call_hash(HTAB *, char *, int); int __delpair(HTAB *, BUFHEAD *, int); int __expand_table(HTAB *); int __find_bigpair(HTAB *, BUFHEAD *, int, char *, int); u_int16_t __find_last_page(HTAB *, BUFHEAD **); void __free_ovflpage(HTAB *, BUFHEAD *); BUFHEAD *__get_buf(HTAB *, u_int32_t, BUFHEAD *, int); int __get_page(HTAB *, char *, u_int32_t, int, int, int); int __ibitmap(HTAB *, int, int, int); u_int32_t __log2(u_int32_t); int __put_page(HTAB *, char *, u_int32_t, int, int); int __split_page(HTAB *, u_int32_t, u_int32_t); #ifdef DEBUG void __bt_dnpage(DB *, pgno_t); void __bt_dpage(PAGE *); void __bt_dump(DB *); #endif /* ifdef DEBUG */ ================================================ FILE: common/args.h ================================================ /* $OpenBSD: args.h,v 1.5 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)args.h 10.2 (Berkeley) 3/6/96 */ /* * Structure for building "argc/argv" vector of arguments. * * !!! * All arguments are NULL terminated as well as having an associated length. * The argument vector is NOT necessarily NULL terminated. The proper way * to check the number of arguments is to use the argc value in the EXCMDARG * structure or to walk the array until an ARGS structure with a length of 0 * is found. */ typedef struct _args { CHAR_T *bp; /* Argument. */ size_t blen; /* Buffer length. */ size_t len; /* Argument length. */ #define A_ALLOCATED 0x01 /* If allocated space. */ u_int8_t flags; } ARGS; ================================================ FILE: common/common.h ================================================ /* $OpenBSD: common.h,v 1.10 2021/01/26 18:19:43 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)common.h 10.13 (Berkeley) 9/25/96 */ #include "../include/compat.h" #include #include #include /* * Forward structure declarations. Not pretty, but the include files * are far too interrelated for a clean solution. */ typedef struct _cb CB; typedef struct _event EVENT; typedef struct _excmd EXCMD; typedef struct _exf EXF; typedef struct _fref FREF; typedef struct _gs GS; typedef struct _lmark LMARK; typedef struct _mark MARK; typedef struct _msg MSGS; typedef struct _option OPTION; typedef struct _optlist OPTLIST; typedef struct _scr SCR; typedef struct _script SCRIPT; typedef struct _seq SEQ; typedef struct _tag TAG; typedef struct _tagf TAGF; typedef struct _tagq TAGQ; typedef struct _text TEXT; /* Autoindent state. */ typedef enum { C_NOTSET, C_CARATSET, C_ZEROSET } carat_t; /* Busy message types. */ typedef enum { BUSY_ON = 1, BUSY_OFF, BUSY_UPDATE } busy_t; /* * Routines that return a confirmation return: * * CONF_NO User answered no. * CONF_QUIT User answered quit, eof or an error. * CONF_YES User answered yes. */ typedef enum { CONF_NO, CONF_QUIT, CONF_YES } conf_t; /* Directions. */ typedef enum { NOTSET, FORWARD, BACKWARD } dir_t; /* Line operations. */ typedef enum { LINE_APPEND, LINE_DELETE, LINE_INSERT, LINE_RESET } lnop_t; /* Lock return values. */ typedef enum { LOCK_FAILED, LOCK_SUCCESS, LOCK_UNAVAIL } lockr_t; /* Sequence types. */ typedef enum { SEQ_ABBREV, SEQ_COMMAND, SEQ_INPUT } seq_t; /* Program modes. */ extern enum pmode { MODE_EX, MODE_VI, MODE_VIEW } pmode; /* * Extensions to POSIX. */ #ifndef ACCESSPERMS # define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO) #endif /* ifndef ACCESSPERMS */ #ifndef ALLPERMS # define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) #endif /* ifndef ALLPERMS */ /* * Local includes. */ #include "key.h" /* Required by args.h. */ #include "args.h" /* Required by options.h. */ #include "options.h" /* Required by screen.h. */ #include "msg.h" /* Required by gs.h. */ #include "cut.h" /* Required by gs.h. */ #include "seq.h" /* Required by screen.h. */ #include "util.h" /* Required by ex.h. */ #include "mark.h" /* Required by gs.h. */ #include "../ex/ex.h" /* Required by gs.h. */ #include "gs.h" /* Required by screen.h. */ #include "screen.h" /* Required by exf.h. */ #include "exf.h" #include "log.h" #include "mem.h" #include "com_extern.h" ================================================ FILE: common/cut.c ================================================ /* $OpenBSD: cut.c,v 1.18 2025/07/30 22:19:13 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" static void cb_rotate(SCR *); /* * cut -- * Put a range of lines/columns into a TEXT buffer. * * There are two buffer areas, both found in the global structure. The first * is the linked list of all the buffers the user has named, the second is the * unnamed buffer storage. There is a pointer, too, which is the current * default buffer, i.e. it may point to the unnamed buffer or a named buffer * depending on into what buffer the last text was cut. Logically, in both * delete and yank operations, if the user names a buffer, the text is cut * into it. If it's a delete of information on more than a single line, the * contents of the numbered buffers are rotated up one, the contents of the * buffer named '9' are discarded, and the text is cut into the buffer named * '1'. The text is always cut into the unnamed buffer. * * In all cases, upper-case buffer names are the same as lower-case names, * with the exception that they cause the buffer to be appended to instead * of replaced. Note, however, that if text is appended to a buffer, the * default buffer only contains the appended text, not the entire contents * of the buffer. * * !!! * The contents of the default buffer would disappear after most operations * in historic vi. It's unclear that this is useful, so we don't bother. * * When users explicitly cut text into the numeric buffers, historic vi became * genuinely strange. I've never been able to figure out what was supposed to * happen. It behaved differently if you deleted text than if you yanked text, * and, in the latter case, the text was appended to the buffer instead of * replacing the contents. Hopefully it's not worth getting right, and here * we just treat the numeric buffers like any other named buffer. * * PUBLIC: int cut(SCR *, CHAR_T *, MARK *, MARK *, int); */ int cut(SCR *sp, CHAR_T *namep, MARK *fm, MARK *tm, int flags) { CB *cbp; CHAR_T name = '1'; /* default numeric buffer */ recno_t lno; int append, copy_one, copy_def; /* Check if the line numbers are out-of-band. */ if (fm->lno == OOBLNO || tm->lno == OOBLNO) return (1); /* * If the user specified a buffer, put it there. (This may require * a copy into the numeric buffers. We do the copy so that we don't * have to reference count and so we don't have to deal with things * like appends to buffers that are used multiple times.) * * Otherwise, if it's supposed to be put in a numeric buffer (usually * a delete) put it there. The rules for putting things in numeric * buffers were historically a little strange. There were three cases. * * 1: Some motions are always line mode motions, which means * that the cut always goes into the numeric buffers. * 2: Some motions aren't line mode motions, e.g. d10w, but * can cross line boundaries. For these commands, if the * cut crosses a line boundary, it goes into the numeric * buffers. This includes most of the commands. * 3: Some motions aren't line mode motions, e.g. d`, * but always go into the numeric buffers, regardless. This * was the commands: % ` / ? ( ) N n { } -- and nvi adds ^A. * * Otherwise, put it in the unnamed buffer. */ append = copy_one = copy_def = 0; if (namep != NULL) { name = *namep; if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) && (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) { copy_one = 1; cb_rotate(sp); } if ((append = isupper(name)) == 1) { if (!copy_one) copy_def = 1; name = tolower(name); } namecb: CBNAME(sp, cbp, name); } else if (LF_ISSET(CUT_NUMREQ) || (LF_ISSET(CUT_NUMOPT) && (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno))) { /* Copy into numeric buffer 1. */ cb_rotate(sp); goto namecb; } else cbp = &sp->gp->dcb_store; copyloop: /* * If this is a new buffer, create it and add it into the list. * Otherwise, if it's not an append, free its current contents. */ if (cbp == NULL) { CALLOC_RET(sp, cbp, 1, sizeof(CB)); cbp->name = name; TAILQ_INIT(&cbp->textq); LIST_INSERT_HEAD(&sp->gp->cutq, cbp, q); } else if (!append) { text_lfree(&cbp->textq); cbp->len = 0; cbp->flags = 0; } /* In line mode, it's pretty easy, just cut the lines. */ if (LF_ISSET(CUT_LINEMODE)) { cbp->flags |= CB_LMODE; for (lno = fm->lno; lno <= tm->lno; ++lno) if (cut_line(sp, lno, 0, CUT_LINE_TO_EOL, cbp)) goto cut_line_err; } else { /* * Get the first line. A length of CUT_LINE_TO_EOL causes * cut_line() to cut from the MARK to the end of the line. */ if (cut_line(sp, fm->lno, fm->cno, fm->lno != tm->lno ? CUT_LINE_TO_EOL : (tm->cno - fm->cno) + 1, cbp)) goto cut_line_err; /* Get the intermediate lines. */ for (lno = fm->lno; ++lno < tm->lno;) if (cut_line(sp, lno, 0, CUT_LINE_TO_EOL, cbp)) goto cut_line_err; /* Get the last line. */ if (tm->lno != fm->lno && cut_line(sp, lno, 0, tm->cno + 1, cbp)) goto cut_line_err; } append = 0; /* Only append to the named buffer. */ sp->gp->dcbp = cbp; /* Repoint the default buffer on each pass. */ if (copy_one) { /* Copy into numeric buffer 1. */ CBNAME(sp, cbp, name); copy_one = 0; goto copyloop; } if (copy_def) { /* Copy into the default buffer. */ cbp = &sp->gp->dcb_store; copy_def = 0; goto copyloop; } return (0); cut_line_err: text_lfree(&cbp->textq); cbp->len = 0; cbp->flags = 0; sp->gp->dcbp = NULL; return (1); } /* * cb_rotate -- * Rotate the numbered buffers up one. */ static void cb_rotate(SCR *sp) { CB *cbp, *del_cbp; del_cbp = NULL; LIST_FOREACH(cbp, &sp->gp->cutq, q) switch(cbp->name) { case '1': cbp->name = '2'; break; case '2': cbp->name = '3'; break; case '3': cbp->name = '4'; break; case '4': cbp->name = '5'; break; case '5': cbp->name = '6'; break; case '6': cbp->name = '7'; break; case '7': cbp->name = '8'; break; case '8': cbp->name = '9'; break; case '9': del_cbp = cbp; break; } if (del_cbp != NULL) { LIST_REMOVE(del_cbp, q); text_lfree(&del_cbp->textq); free(del_cbp); } } /* * cut_line -- * Cut a portion of a single line. * * PUBLIC: int cut_line(SCR *, recno_t, size_t, size_t, CB *); */ int cut_line(SCR *sp, recno_t lno, size_t fcno, size_t clen, CB *cbp) { TEXT *tp; size_t len; char *p; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); /* Create a TEXT structure that can hold the entire line. */ if ((tp = text_init(sp, NULL, 0, len)) == NULL) return (1); /* * If the line isn't empty and it's not the entire line, * copy the portion we want, and reset the TEXT length. */ if (len != 0) { if (clen == CUT_LINE_TO_EOL) clen = len - fcno; memcpy(tp->lb, p + fcno, clen); tp->len = clen; } /* Append to the end of the cut buffer. */ TAILQ_INSERT_TAIL(&cbp->textq, tp, q); cbp->len += tp->len; return (0); } /* * cut_close -- * Discard all cut buffers. * * PUBLIC: void cut_close(GS *); */ void cut_close(GS *gp) { CB *cbp; /* Free cut buffer list. */ while ((cbp = LIST_FIRST(&gp->cutq)) != NULL) { if (!TAILQ_EMPTY(&cbp->textq)) text_lfree(&cbp->textq); LIST_REMOVE(cbp, q); free(cbp); } /* Free default cut storage. */ cbp = &gp->dcb_store; if (!TAILQ_EMPTY(&cbp->textq)) text_lfree(&cbp->textq); } /* * text_init -- * Allocate a new TEXT structure. * * PUBLIC: TEXT *text_init(SCR *, const char *, size_t, size_t); */ TEXT * text_init(SCR *sp, const char *p, size_t len, size_t total_len) { TEXT *tp; CALLOC(sp, tp, 1, sizeof(TEXT)); if (tp == NULL) return (NULL); /* ANSI C doesn't define a call to malloc(3) for 0 bytes. */ if ((tp->lb_len = total_len) != 0) { MALLOC(sp, tp->lb, tp->lb_len); if (tp->lb == NULL) { free(tp); return (NULL); } if (p != NULL && len != 0) memcpy(tp->lb, p, len); } tp->len = len; return (tp); } /* * text_lfree -- * Free a chain of text structures. * * PUBLIC: void text_lfree(TEXTH *); */ void text_lfree(TEXTH *headp) { TEXT *tp; while ((tp = TAILQ_FIRST(headp))) { TAILQ_REMOVE(headp, tp, q); text_free(tp); } } /* * text_free -- * Free a text structure. * * PUBLIC: void text_free(TEXT *); */ void text_free(TEXT *tp) { free(tp->lb); free(tp); } ================================================ FILE: common/cut.h ================================================ /* $OpenBSD: cut.h,v 1.9 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)cut.h 10.5 (Berkeley) 4/3/96 */ typedef struct _texth TEXTH; /* TEXT list head structure. */ TAILQ_HEAD(_texth, _text); /* Cut buffers. */ struct _cb { LIST_ENTRY(_cb) q; /* Linked list of cut buffers. */ TEXTH textq; /* Linked list of TEXT structures. */ CHAR_T name; /* Cut buffer name. */ size_t len; /* Total length of cut text. */ #define CB_LMODE 0x01 /* Cut was in line mode. */ u_int8_t flags; }; /* Lines/blocks of text. */ struct _text { /* Text: a linked list of lines. */ TAILQ_ENTRY(_text) q; /* Linked list of text structures. */ char *lb; /* Line buffer. */ size_t lb_len; /* Line buffer length. */ size_t len; /* Line length. */ /* These fields are used by the vi text input routine. */ recno_t lno; /* 1-N: file line. */ size_t cno; /* 0-N: file character in line. */ size_t ai; /* 0-N: autoindent bytes. */ size_t insert; /* 0-N: bytes to insert (push). */ size_t offset; /* 0-N: initial, unerasable chars. */ size_t owrite; /* 0-N: chars to overwrite. */ size_t R_erase; /* 0-N: 'R' erase count. */ size_t sv_cno; /* 0-N: Saved line cursor. */ size_t sv_len; /* 0-N: Saved line length. */ /* * These fields returns information from the vi text input routine. * * The termination condition. Note, this field is only valid if the * text input routine returns success. * TERM_BS: User backspaced over the prompt. * TERM_CEDIT: User entered . * TERM_CR: User entered ; no data. * TERM_ESC: User entered ; no data. * TERM_OK: Data available. * TERM_SEARCH: Incremental search. */ enum { TERM_BS, TERM_CEDIT, TERM_CR, TERM_ESC, TERM_OK, TERM_SEARCH } term; }; /* * Get named buffer 'name'. * Translate upper-case buffer names to lower-case buffer names. */ #define CBNAME(sp, cbp, nch) { \ CHAR_T L__name; \ L__name = isupper(nch) ? tolower(nch) : (nch); \ LIST_FOREACH((cbp), &(sp)->gp->cutq, q) \ if ((cbp)->name == L__name) \ break; \ } /* Flags to the cut() routine. */ #define CUT_LINEMODE 0x01 /* Cut in line mode. */ #define CUT_NUMOPT 0x02 /* Numeric buffer: optional. */ #define CUT_NUMREQ 0x04 /* Numeric buffer: required. */ /* Special length to cut_line(). */ #define CUT_LINE_TO_EOL ((size_t) -1) /* Cut to the end of line. */ ================================================ FILE: common/delete.c ================================================ /* $OpenBSD: delete.c,v 1.12 2017/11/26 09:59:41 mestre Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "common.h" /* * del -- * Delete a range of text. * * PUBLIC: int del(SCR *, MARK *, MARK *, int); */ int del(SCR *sp, MARK *fm, MARK *tm, int lmode) { recno_t lno; size_t blen, len, nlen, tlen; char *bp, *p; int eof, rval; bp = NULL; /* Case 1 -- delete in line mode. */ if (lmode) { for (lno = tm->lno; lno >= fm->lno; --lno) { if (db_delete(sp, lno)) return (1); ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } goto done; } /* * Case 2 -- delete to EOF. This is a special case because it's * easier to pick it off than try and find it in the other cases. */ if (db_last(sp, &lno)) return (1); if (tm->lno >= lno) { if (tm->lno == lno) { if (db_get(sp, lno, DBG_FATAL, &p, &len)) return (1); eof = tm->cno != -1 && tm->cno >= len ? 1 : 0; } else eof = 1; if (eof) { for (lno = tm->lno; lno > fm->lno; --lno) { if (db_delete(sp, lno)) return (1); ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } if (db_get(sp, fm->lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RET(sp, bp, blen, fm->cno); if (bp == NULL) return (1); memcpy(bp, p, fm->cno); if (db_set(sp, fm->lno, bp, fm->cno)) return (1); goto done; } } /* Case 3 -- delete within a single line. */ if (tm->lno == fm->lno) { if (db_get(sp, fm->lno, DBG_FATAL, &p, &len)) return (1); if (len != 0) { GET_SPACE_RET(sp, bp, blen, len); if (bp == NULL) goto err; if (fm->cno != 0) memcpy(bp, p, fm->cno); memcpy(bp + fm->cno, p + (tm->cno + 1), len - (tm->cno + 1)); if (db_set(sp, fm->lno, bp, len - ((tm->cno - fm->cno) + 1))) goto err; goto done; } } /* * Case 4 -- delete over multiple lines. * * Copy the start partial line into place. */ if ((tlen = fm->cno) != 0) { if (db_get(sp, fm->lno, DBG_FATAL, &p, NULL)) return (1); GET_SPACE_RET(sp, bp, blen, tlen + 256); if (bp == NULL) return (1); memcpy(bp, p, tlen); } /* Copy the end partial line into place. */ if (db_get(sp, tm->lno, DBG_FATAL, &p, &len)) goto err; if (len != 0 && tm->cno != len - 1) { if (len < tm->cno + 1 || len - (tm->cno + 1) > SIZE_MAX - tlen) { msgq(sp, M_ERR, "Line length overflow"); goto err; } nlen = (len - (tm->cno + 1)) + tlen; if (tlen == 0) { GET_SPACE_RET(sp, bp, blen, nlen); } else ADD_SPACE_RET(sp, bp, blen, nlen); if (bp == NULL) goto err; memcpy(bp + tlen, p + (tm->cno + 1), len - (tm->cno + 1)); tlen += len - (tm->cno + 1); } /* Set the current line. */ if (db_set(sp, fm->lno, bp, tlen)) goto err; /* Delete the last and intermediate lines. */ for (lno = tm->lno; lno > fm->lno; --lno) { if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp)) break; } done: rval = 0; if (0) err: rval = 1; if (bp != NULL) FREE_SPACE(sp, bp, blen); return (rval); } ================================================ FILE: common/exf.c ================================================ /* $OpenBSD: exf.c,v 1.50 2024/02/15 00:55:01 jsg Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #undef open /* * We include , because the flock(2) and open(2) #defines * were found there on historical systems. We also include * because the open(2) #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include #include #if defined(__solaris__) # define __EXTENSIONS__ # include # include #endif /* if defined(__solaris__) */ #include #include #include #include #include #include "common.h" static int file_backup(SCR *, char *, char *); static void file_cinit(SCR *); static void file_comment(SCR *); static int file_spath(SCR *, FREF *, struct stat *, int *); /* * file_add -- * Insert a file name into the FREF list, if it doesn't already * appear in it. * * !!! * The "if it doesn't already appear" changes vi's semantics slightly. If * you do a "vi foo bar", and then execute "next bar baz", the edit of bar * will reflect the line/column of the previous edit session. Historic nvi * did not do this. The change is a logical extension of the change where * vi now remembers the last location in any file that it has ever edited, * not just the previously edited file. * * PUBLIC: FREF *file_add(SCR *, CHAR_T *); */ FREF * file_add(SCR *sp, CHAR_T *name) { GS *gp; FREF *frp, *tfrp; /* * Return it if it already exists. Note that we test against the * user's name, whatever that happens to be, including if it's a * temporary file. * * If the user added a file but was unable to initialize it, there * can be file list entries where the name field is NULL. Discard * them the next time we see them. */ gp = sp->gp; if (name != NULL) TAILQ_FOREACH_SAFE(frp, &gp->frefq, q, tfrp) { if (frp->name == NULL) { TAILQ_REMOVE(&gp->frefq, frp, q); free(frp->name); free(frp); continue; } if (!strcmp(frp->name, name)) return (frp); } /* Allocate and initialize the FREF structure. */ CALLOC(sp, frp, 1, sizeof(FREF)); if (frp == NULL) return (NULL); /* * If no file name specified, or if the file name is a request * for something temporary, file_init() will allocate the file * name. Temporary files are always ignored. */ if (name != NULL && strcmp(name, TEMPORARY_FILE_STRING) && (frp->name = strdup(name)) == NULL) { free(frp); msgq(sp, M_SYSERR, NULL); return (NULL); } /* Append into the chain of file names. */ TAILQ_INSERT_TAIL(&gp->frefq, frp, q); return (frp); } /* * file_init -- * Start editing a file, based on the FREF structure. If successful, * let go of any previous file. Don't release the previous file until * absolutely sure we have the new one. * * PUBLIC: int file_init(SCR *, FREF *, char *, int); */ int file_init(SCR *sp, FREF *frp, char *rcv_name, int flags) { EXF *ep; RECNOINFO oinfo; struct stat sb; size_t psize; int fd, exists, open_err, readonly; char *oname, tname[] = "/tmp/vi.XXXXXX"; open_err = readonly = 0; /* * If the file is a recovery file, let the recovery code handle it. * Clear the FR_RECOVER flag first -- the recovery code does set up, * and then calls us! If the recovery call fails, it's probably * because the named file doesn't exist. So, move boldly forward, * presuming that there's an error message the user will get to see. */ if (F_ISSET(frp, FR_RECOVER)) { F_CLR(frp, FR_RECOVER); if (rcv_read(sp, frp) == 0) return (0); /* successful recovery */ } /* * Required FRP initialization; the only flag we keep is the * cursor information. */ F_CLR(frp, ~FR_CURSORSET); /* * Required EXF initialization: * Flush the line caches. * Default recover mail file fd to -1. * Set initial EXF flag bits. */ CALLOC_RET(sp, ep, 1, sizeof(EXF)); ep->c_lno = ep->c_nlines = OOBLNO; ep->rcv_fd = ep->fcntl_fd = -1; F_SET(ep, F_FIRSTMODIFY); /* * Scan the user's path to find the file that we're going to * try and open. */ if (file_spath(sp, frp, &sb, &exists)) { free(ep); return (1); } /* * If no name or backing file, for whatever reason, create a backing * temporary file, saving the temp file name so we can later unlink * it. If the user never named this file, copy the temporary file name * to the real name (we display that until the user renames it). */ oname = frp->name; /* * User is editing a named file that doesn't exist yet other than as a * temporary file. */ if (!exists && oname != NULL && frp->tname != NULL) { free(ep); return (1); } if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) { /* * Don't try to create a temporary support file twice. */ if (frp->tname != NULL) goto err; fd = mkstemp(tname); if (fd == -1 || fstat(fd, &sb) == -1 || fchmod(fd, S_IRUSR | S_IWUSR) == -1) { msgq(sp, M_SYSERR, "Unable to create temporary file"); if (fd != -1) { close(fd); (void)unlink(tname); } goto err; } (void)close(fd); if (frp->name == NULL) F_SET(frp, FR_TMPFILE); if ((frp->tname = strdup(tname)) == NULL || (frp->name == NULL && (frp->name = strdup(tname)) == NULL)) { free(frp->tname); frp->tname = NULL; msgq(sp, M_SYSERR, NULL); (void)unlink(tname); goto err; } oname = frp->tname; psize = 1024; if (!LF_ISSET(FS_OPENERR)) F_SET(frp, FR_NEWFILE); } else { /* * XXX * A seat of the pants calculation: try to keep the file in * 15 pages or less. Don't use a page size larger than 10K * (vi should have good locality) or smaller than 1K. */ psize = ((sb.st_size / 15) + 1023) / 1024; if (psize >= 8) psize=8<<10; else if (psize >= 4) psize=4<<10; else if (psize >= 2) psize=2<<10; else psize=1<<10; if (!S_ISREG(sb.st_mode)) msgq_str(sp, M_ERR, oname, "Warning: %s is not a regular file"); } /* Save device, inode and modification time. */ F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtim = sb.st_mtim; /* Set up recovery. */ memset(&oinfo, 0, sizeof(RECNOINFO)); oinfo.bval = '\n'; /* Always set. */ oinfo.psize = psize; oinfo.flags = F_ISSET(sp->gp, G_SNAPSHOT) ? R_SNAPSHOT : 0; #ifndef NO_BFNAME if (rcv_name == NULL) { if (!rcv_tmp(sp, ep, frp->name)) oinfo.bfname = ep->rcv_path; } else { if ((ep->rcv_path = strdup(rcv_name)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } oinfo.bfname = ep->rcv_path; F_SET(ep, F_MODIFIED); } #endif /* ifndef NO_BFNAME */ /* Open a db structure. */ if ((ep->db = dbopen(rcv_name == NULL ? oname : NULL, O_NONBLOCK | O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, DB_RECNO, &oinfo)) == NULL) { msgq_str(sp, M_SYSERR, rcv_name == NULL ? oname : rcv_name, "%s"); /* * !!! * Historically, vi permitted users to edit files that couldn't * be read. This isn't useful for single files from a command * line, but it's quite useful for "vi *.c", since you can skip * past files that you can't read. */ open_err = 1; goto oerr; } /* * Do the remaining things that can cause failure of the new file, * mark and logging initialization. */ if (mark_init(sp, ep) || log_init(sp, ep)) goto err; /* * Set the alternate file name to be the file we're discarding. * * !!! * Temporary files can't become alternate files, so there's no file * name. This matches historical practice, although it could only * happen in historical vi as the result of the initial command, i.e. * if vi was executed without a file name. */ if (LF_ISSET(FS_SETALT)) set_alt_name(sp, sp->frp == NULL || F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name); /* * Close the previous file; if that fails, close the new one and run * for the border. * * !!! * There's a nasty special case. If the user edits a temporary file, * and then does an ":e! %", we need to re-initialize the backing * file, but we can't change the name. (It's worse -- we're dealing * with *names* here, we can't even detect that it happened.) Set a * flag so that the file_end routine ignores the backing information * of the old file if it happens to be the same as the new one. * * !!! * Side-effect: after the call to file_end(), sp->frp may be NULL. */ if (sp->ep != NULL) { F_SET(frp, FR_DONTDELETE); if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) { (void)file_end(sp, ep, 1); goto err; } F_CLR(frp, FR_DONTDELETE); } /* * Lock the file; if it's a recovery file, it should already be * locked. Note, we acquire the lock after the previous file * has been ended, so that we don't get an "already locked" error * for ":edit!". * * XXX * While the user can't interrupt us between the open and here, * there's a race between the dbopen() and the lock. Not much * we can do about it. * * XXX * We don't make a big deal of not being able to lock the file. As * locking rarely works over NFS, and often fails if the file was * mmap(2)'d, it's far too common to do anything like print an error * message, let alone make the file readonly. At some future time, * when locking is a little more reliable, this should change to be * an error. */ if (rcv_name == NULL && !O_ISSET(sp, O_READONLY)) switch (file_lock(sp, oname, &ep->fcntl_fd, ep->db->fd(ep->db), 0)) { case LOCK_FAILED: F_SET(frp, FR_UNLOCKED); break; case LOCK_UNAVAIL: readonly = 1; msgq_str(sp, M_INFO, oname, "%s already locked, session is read-only"); break; case LOCK_SUCCESS: break; } /* * Historically, the readonly edit option was set per edit buffer in * vi, unless the -R command-line option was specified or the program * was executed as "view". (Well, to be truthful, if the letter 'w' * occurred anywhere in the program name, but let's not get into that.) * So, the persistent readonly state has to be stored in the screen * structure, and the edit option value toggles with the contents of * the edit buffer. If the persistent readonly flag is set, set the * readonly edit option. * * Otherwise, try and figure out if a file is readonly. This is a * dangerous thing to do. The kernel is the only arbiter of whether * or not a file is writeable, and the best that a user program can * do is guess. Obvious loopholes are files that are on a file system * mounted readonly (access catches this one on a few systems), or * alternate protection mechanisms, ACL's for example, that we can't * portably check. Lots of fun, and only here because users whined. * * !!! * Historic vi displayed the readonly message if none of the file * write bits were set, or if an an access(2) call on the path * failed. This seems reasonable. If the file is mode 444, root * users may want to know that the owner of the file did not expect * it to be written. * * Historic vi set the readonly bit if no write bits were set for * a file, even if the access call would have succeeded. This makes * the superuser force the write even when vi expects that it will * succeed. I'm less supportive of this semantic, but it's historic * practice and the conservative approach to vi'ing files as root. * * It would be nice if there was some way to update this when the user * does a "^Z; chmod ...". The problem is that we'd first have to * distinguish between readonly bits set because of file permissions * and those set for other reasons. That's not too hard, but deciding * when to reevaluate the permissions is trickier. An alternative * might be to turn off the readonly bit if the user forces a write * and it succeeds. * * XXX * Access(2) doesn't consider the effective uid/gid values. This * probably isn't a problem for vi when it's running standalone. */ if (readonly || F_ISSET(sp, SC_READONLY) || (!F_ISSET(frp, FR_NEWFILE) && (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) || access(frp->name, W_OK)))) O_SET(sp, O_READONLY); else O_CLR(sp, O_READONLY); /* Switch... */ ++ep->refcnt; sp->ep = ep; sp->frp = frp; /* Set the initial cursor position, queue initial command. */ file_cinit(sp); /* Redraw the screen from scratch, schedule a welcome message. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); if (frp->lno == OOBLNO) F_SET(sp, SC_SCR_TOP); return (0); err: free(frp->name); frp->name = NULL; if (frp->tname != NULL) { (void)unlink(frp->tname); free(frp->tname); frp->tname = NULL; } oerr: if (F_ISSET(ep, F_RCV_ON)) (void)unlink(ep->rcv_path); free(ep->rcv_path); ep->rcv_path = NULL; if (ep->db != NULL) (void)ep->db->close(ep->db); free(ep); return (open_err ? file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1); } /* * file_spath -- * Scan the user's path to find the file that we're going to * try and open. */ static int file_spath(SCR *sp, FREF *frp, struct stat *sbp, int *existsp) { CHAR_T savech; size_t len; int found; char *name, *p, *t, path[PATH_MAX]; /* * If the name is NULL or an explicit reference (i.e., the first * component is . or ..) ignore the O_PATH option. */ name = frp->name; if (name == NULL) { *existsp = 0; return (0); } if (name[0] == '/' || (name[0] == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/')))) { *existsp = !stat(name, sbp); return (0); } /* Try . */ if (!stat(name, sbp)) { *existsp = 1; return (0); } /* Try the O_PATH option values. */ for (found = 0, p = t = O_STR(sp, O_PATH);; ++p) if (*p == ':' || *p == '\0') { if (t < p - 1) { savech = *p; *p = '\0'; len = snprintf(path, sizeof(path), "%s/%s", t, name); if (len >= sizeof(path)) len = sizeof(path) - 1; *p = savech; if (!stat(path, sbp)) { found = 1; break; } } t = p + 1; if (*p == '\0') break; } /* If we found it, build a new pathname and discard the old one. */ if (found) { MALLOC_RET(sp, p, len + 1); memcpy(p, path, len + 1); free(frp->name); frp->name = p; } *existsp = found; return (0); } /* * file_cinit -- * Set up the initial cursor position. */ static void file_cinit(SCR *sp) { GS *gp; MARK m; size_t len; int nb; /* Set some basic defaults. */ sp->lno = 1; sp->cno = 0; /* * Historically, initial commands (the -c option) weren't executed * until a file was loaded, e.g. "vi +10 nofile", followed by an * :edit or :tag command, would execute the +10 on the file loaded * by the subsequent command, (assuming that it existed). This * applied as well to files loaded using the tag commands, and we * follow that historic practice. Also, all initial commands were * ex commands and were always executed on the last line of the file. * * Otherwise, if no initial command for this file: * If in ex mode, move to the last line, first nonblank character. * If the file has previously been edited, move to the last known * position, and check it for validity. * Otherwise, move to the first line, first nonblank. * * This gets called by the file init code, because we may be in a * file of ex commands and we want to execute them from the right * location in the file. */ nb = 0; gp = sp->gp; if (gp->C_option != NULL) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } if (ex_run_str(sp, "-C option", gp->C_option, strlen(gp->C_option), 1, 1)) return; gp->C_option = NULL; gp->c_option = NULL; } else if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } if (ex_run_str(sp, "-c option", gp->c_option, strlen(gp->c_option), 1, 1)) return; gp->c_option = NULL; } else if (F_ISSET(sp, SC_EX)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; return; } nb = 1; } else { if (F_ISSET(sp->frp, FR_CURSORSET)) { sp->lno = sp->frp->lno; sp->cno = sp->frp->cno; /* If returning to a file in vi, center the line. */ F_SET(sp, SC_SCR_CENTER); } else { if (O_ISSET(sp, O_COMMENT)) file_comment(sp); else sp->lno = 1; nb = 1; } if (db_get(sp, sp->lno, 0, NULL, &len)) { sp->lno = 1; sp->cno = 0; return; } if (!nb && sp->cno > len) nb = 1; } if (nb) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * !!! * The initial column is also the most attractive column. */ sp->rcm = sp->cno; /* * !!! * Historically, vi initialized the absolute mark, but ex did not. * Which meant, that if the first command in ex mode was "visual", * or if an ex command was executed first (e.g. vi +10 file) vi was * entered without the mark being initialized. For consistency, if * the file isn't empty, we initialize it for everyone, believing * that it can't hurt, and is generally useful. Not initializing it * if the file is empty is historic practice, although it has always * been possible to set (and use) marks in empty vi files. */ m.lno = sp->lno; m.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &m, 0); } /* * file_end -- * Stop editing a file. * * PUBLIC: int file_end(SCR *, EXF *, int); */ int file_end(SCR *sp, EXF *ep, int force) { FREF *frp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * (If argument ep is NULL, use sp->ep.) * * If multiply referenced, just decrement the count and return. */ if (ep == NULL) ep = sp->ep; if (--ep->refcnt != 0) return (0); /* * * Clean up the FREF structure. * * Save the cursor location. * * XXX * It would be cleaner to do this somewhere else, but by the time * ex or vi knows that we're changing files it's already happened. */ frp = sp->frp; if (frp == NULL) return (1); frp->lno = sp->lno; frp->cno = sp->cno; F_SET(frp, FR_CURSORSET); /* * We may no longer need the temporary backing file, so clean it * up. We don't need the FREF structure either, if the file was * never named, so lose it. * * !!! * Re: FR_DONTDELETE, see the comment above in file_init(). */ if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) { if (unlink(frp->tname)) msgq_str(sp, M_SYSERR, frp->tname, "%s: remove"); free(frp->tname); frp->tname = NULL; if (F_ISSET(frp, FR_TMPFILE)) { TAILQ_REMOVE(&sp->gp->frefq, frp, q); free(frp->name); free(frp); frp = NULL; } sp->frp = NULL; } /* * Clean up the EXF structure. * * Close the db structure. */ if (ep->db->close != NULL && ep->db->close(ep->db) && !force) { if (frp) msgq_str(sp, M_SYSERR, frp->name, "%s: close"); else msgq(sp, M_SYSERR, "close"); ++ep->refcnt; return (1); } /* COMMITTED TO THE CLOSE. THERE'S NO GOING BACK... */ /* Stop logging. */ (void)log_end(sp, ep); /* Free up any marks. */ (void)mark_end(sp, ep); /* * Delete recovery files, close the open descriptor, free recovery * memory. See recover.c for a description of the protocol. * * XXX * Unlink backup file first, we can detect that the recovery file * doesn't reference anything when the user tries to recover it. * There's a race, here, obviously, but it's fairly small. */ if (!F_ISSET(ep, F_RCV_NORM)) { if (ep->rcv_path != NULL && unlink(ep->rcv_path)) msgq_str(sp, M_SYSERR, ep->rcv_path, "%s: remove"); if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath)) msgq_str(sp, M_SYSERR, ep->rcv_mpath, "%s: remove"); } if (ep->fcntl_fd != -1) (void)close(ep->fcntl_fd); if (ep->rcv_fd != -1) (void)close(ep->rcv_fd); free(ep->rcv_path); free(ep->rcv_mpath); free(ep); return (0); } /* * file_write -- * Write the file to disk. Historic vi had fairly convoluted * semantics for whether or not writes would happen. That's * why all the flags. * * PUBLIC: int file_write(SCR *, MARK *, MARK *, char *, int); */ int file_write(SCR *sp, MARK *fm, MARK *tm, char *name, int flags) { enum { NEWFILE, OLDFILE } mtype; struct stat sb; EXF *ep; FILE *fp; FREF *frp; MARK from, to; size_t len; unsigned long nlno, nch; int fd, nf, noname, oflags, rval; char *p, *s, *t, buf[PATH_MAX + 64]; const char *msgstr; ep = sp->ep; frp = sp->frp; /* * Writing '%', or naming the current file explicitly, has the * same semantics as writing without a name. */ if (name == NULL || !strcmp(name, frp->name)) { noname = 1; name = frp->name; } else noname = 0; /* Can't write files marked read-only, unless forced. */ if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "Read-only file, not written; use ! to override" : "Read-only file, not written"); return (1); } /* If not forced, not appending, and "writeany" not set ... */ if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) { /* Don't overwrite anything but the original file. */ if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) && !stat(name, &sb)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "%s exists, not written; use ! to override" : "%s exists, not written"); return (1); } /* * Don't write part of any existing file. Only test for the * original file, the previous test catches anything else. */ if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "Partial file, not written; use ! to override" : "Partial file, not written"); return (1); } } /* * Figure out if the file already exists -- if it doesn't, we display * the "new file" message. The stat might not be necessary, but we * just repeat it because it's easier than hacking the previous tests. * The information is only used for the user message and modification * time test, so we can ignore the obvious race condition. * * One final test. If we're not forcing or appending the current file, * and we have a saved modification time, object if the file changed * since we last edited or wrote it, and make them force it. */ if (stat(name, &sb)) mtype = NEWFILE; else { if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) && ((F_ISSET(ep, F_DEVSET) && (sb.st_dev != ep->mdev || sb.st_ino != ep->minode)) || timespeccmp(&sb.st_mtim, &ep->mtim, !=))) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "%s: file modified more recently than this copy; use ! to override" : "%s: file modified more recently than this copy"); return (1); } mtype = OLDFILE; } /* Set flags to create, write, and either append or truncate. */ oflags = O_CREAT | O_WRONLY | (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC); /* Backup the file if requested. */ if (!opts_empty(sp, O_BACKUP, 1) && file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE)) return (1); /* Open the file. */ if ((fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } /* Try and get a lock. */ if (!noname && file_lock(sp, NULL, NULL, fd, 0) == LOCK_UNAVAIL) msgq_str(sp, M_ERR, name, "%s: write lock was unavailable"); /* * Use stdio for buffering. * * XXX * SVR4.2 requires the fdopen mode exactly match the original open * mode, i.e. you have to open with "a" if appending. */ if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? "a" : "w")) == NULL) { msgq_str(sp, M_SYSERR, name, "%s"); (void)close(fd); return (1); } /* Build fake addresses, if necessary. */ if (fm == NULL) { from.lno = 1; from.cno = 0; fm = &from; if (db_last(sp, &to.lno)) { (void)fclose(fp); return (1); } to.cno = 0; tm = &to; } rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0); /* * Save the new last modification time -- even if the write fails * we re-init the time. That way the user can clean up the disk * and rewrite without having to force it. */ if (noname) { if (stat(name, &sb)) (void)clock_gettime(CLOCK_REALTIME, &ep->mtim); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtim = sb.st_mtim; } } /* * If the write failed, complain loudly. ex_writefp() has already * complained about the actual error, reinforce it if data was lost. */ if (rval) { if (!LF_ISSET(FS_APPEND)) msgq_str(sp, M_ERR, name, "%s: WARNING: FILE TRUNCATED"); return (1); } /* * Once we've actually written the file, it doesn't matter that the * file name was changed -- if it was, we've already whacked it. */ F_CLR(frp, FR_NAMECHANGE); /* * If wrote the entire file, and it wasn't by appending it to a file, * clear the modified bit. If the file was written to the original * file name and the file is a temporary, set the "no exit" bit. This * permits the user to write the file and use it in the context of the * filesystem, but still keeps them from discarding their changes by * exiting. */ if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) { F_CLR(ep, F_MODIFIED); if (F_ISSET(frp, FR_TMPFILE)) { if (noname) F_SET(frp, FR_TMPEXIT); else F_CLR(frp, FR_TMPEXIT); } } p = msg_print(sp, name, &nf); switch (mtype) { case NEWFILE: len = snprintf(buf, sizeof(buf), "%s: new file: %'lu lines, %'lu characters", p, nlno, nch); if (len >= sizeof(buf)) len = sizeof(buf) - 1; break; case OLDFILE: msgstr = LF_ISSET(FS_APPEND) ? "%s: appended: %'lu lines, %'lu characters" : "%s: %'lu lines, %'lu characters"; len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); if (len >= sizeof(buf)) len = sizeof(buf) - 1; break; default: abort(); } /* * There's a nasty problem with long path names. Tags files * can result in long paths and vi will request a continuation key from * the user. Unfortunately, the user has typed ahead, and chaos will * result. If we assume that the characters in the filenames only take * a single screen column each, we can trim the filename. */ s = buf; if (len >= sp->cols) { for (s = buf, t = buf + strlen(p); s < t && (*s != '/' || len >= sp->cols - 3); ++s, --len); if (s == t) s = buf; else { *--s = '.'; /* Leading ellipses. */ *--s = '.'; *--s = '.'; } } msgq(sp, M_INFO, "%s", s); if (nf) FREE_SPACE(sp, p, 0); return (0); } /* * file_backup -- * Backup the about-to-be-written file. * * XXX * We do the backup by copying the entire file. It would be nice to do * a rename instead, but: (1) both files may not fit and we want to fail * before doing the rename; (2) the backup file may not be on the same * disk partition as the file being written; (3) there may be optional * file information (MACs, DACs, whatever) that we won't get right if we * recreate the file. So, let's not risk it. */ static int file_backup(SCR *sp, char *name, char *bname) { struct dirent *dp; struct stat sb; DIR *dirp; EXCMD cmd; off_t off; size_t blen; int flags, maxnum, nr, num, nw, rfd, wfd, version; char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192]; rfd = wfd = -1; (void)rfd; bp = estr = wfname = NULL; /* * Open the current file for reading. Do this first, so that * we don't exec a shell before the most likely failure point. * If it doesn't exist, it's okay, there's just nothing to back * up. */ errno = 0; if ((rfd = open(name, O_RDONLY)) < 0) { if (errno == ENOENT) return (0); estr = name; goto err; } /* * If the name starts with an 'N' character, add a version number * to the name. Strip the leading N from the string passed to the * expansion routines, for no particular reason. It would be nice * to permit users to put the version number anywhere in the backup * name, but there isn't a special character that we can use in the * name, and giving a new character a special meaning leads to ugly * hacks both here and in the supporting ex routines. * * Shell and file name expand the option's value. */ argv_init(sp, &cmd); ex_cinit(&cmd, 0, 0, 0, 0, 0, NULL); if (bname[0] == 'N') { version = 1; ++bname; } else version = 0; if (argv_exp2(sp, &cmd, bname, strlen(bname))) { (void)close(rfd); return (1); } /* * 0 args: impossible. * 1 args: use it. * >1 args: object, too many args. */ if (cmd.argc != 1) { msgq_str(sp, M_ERR, bname, "%s expanded into too many file names"); (void)close(rfd); return (1); } /* * If appending a version number, read through the directory, looking * for file names that match the name followed by a number. Make all * of the other % characters in name literal, so the user doesn't get * surprised and sscanf doesn't drop core indirecting through pointers * that don't exist. If any such files are found, increment its number * by one. */ if (version) { GET_SPACE_GOTO(sp, bp, blen, cmd.argv[0]->len * 2 + 50); if (bp == NULL) goto err; for (t = bp, slash = NULL, p = cmd.argv[0]->bp; p[0] != '\0'; *t++ = *p++) if (p[0] == '%') { if (p[1] != '%') *t++ = '%'; } else if (p[0] == '/') slash = t; pct = t; *t++ = '%'; *t++ = 'd'; *t = '\0'; if (slash == NULL) { dirp = opendir("."); p = bp; } else { *slash = '\0'; dirp = opendir(bp); *slash = '/'; p = slash + 1; } if (dirp == NULL) { estr = cmd.argv[0]->bp; goto err; } for (maxnum = 0; (dp = readdir(dirp)) != NULL;) if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum) maxnum = num; (void)closedir(dirp); /* Format the backup file name. */ (void)snprintf(pct, blen - (pct - bp), "%d", maxnum + 1); wfname = bp; } else { bp = NULL; wfname = cmd.argv[0]->bp; } /* Open the backup file, avoiding lurkers. */ if (stat(wfname, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { msgq_str(sp, M_ERR, bname, "%s: not a regular file"); goto err; } if (sb.st_uid != getuid()) { msgq_str(sp, M_ERR, bname, "%s: not owned by you"); goto err; } if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) { msgq_str(sp, M_ERR, bname, "%s: accessible by a user other than the owner"); goto err; } flags = O_TRUNC; } else flags = O_CREAT | O_EXCL; if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || fchmod(wfd, S_IRUSR | S_IWUSR) < 0) { if (wfd != -1) { close(wfd); (void)unlink(wfname); } estr = bname; goto err; } /* Copy the file's current contents to its backup value. */ while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr != 0; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) { estr = wfname; goto err; } if (nr < 0) { estr = name; goto err; } if (close(rfd)) { estr = name; goto err; } if (close(wfd)) { estr = wfname; goto err; } if (bp != NULL) FREE_SPACE(sp, bp, blen); return (0); alloc_err: err: if (rfd != -1) (void)close(rfd); if (wfd != -1) { (void)unlink(wfname); (void)close(wfd); } if (estr) msgq_str(sp, M_SYSERR, estr, "%s"); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (1); } /* * file_comment -- * Skip the first comment. */ static void file_comment(SCR *sp) { recno_t lno; size_t len; char *p; for (lno = 1; !db_get(sp, lno, 0, &p, &len) && len == 0; ++lno); if (p == NULL) return; if (p[0] == '#') { F_SET(sp, SC_SCR_TOP); while (!db_get(sp, ++lno, 0, &p, &len)) if (len < 1 || p[0] != '#') { sp->lno = lno; return; } } else if (len > 1 && p[0] == '/' && p[1] == '*') { F_SET(sp, SC_SCR_TOP); do { for (; len > 1; --len, ++p) if (p[0] == '*' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } else if (len > 1 && p[0] == '/' && p[1] == '/') { F_SET(sp, SC_SCR_TOP); p += 2; len -= 2; do { for (; len > 1; --len, ++p) if (p[0] == '/' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } } /* * file_m1 -- * First modification check routine. The :next, :prev, :rewind, :tag, * :tagpush, :tagpop, ^^ modifications check. * * PUBLIC: int file_m1(SCR *, int, int); */ int file_m1(SCR *sp, int force, int flags) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to write it back or * fail. If autowrite is set, we'll write it back automatically, * unless force is also set. Otherwise, we fail unless forced or * there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED)) { if (O_ISSET(sp, O_AUTOWRITE)) { if (!force && file_aw(sp, flags)) return (1); } else if (ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "File may be modified since last complete write; write or use ! to override" : "File may be modified since last complete write; write or use :edit! to override"); return (1); } } return (file_m3(sp, force)); } /* * file_m2 -- * Second modification check routine. The :edit, :quit, :recover * modifications check. * * PUBLIC: int file_m2(SCR *, int); */ int file_m2(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to fail, unless forced * or there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "File may be modified since last complete write; write or use ! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m3 -- * Third modification check routine. * * PUBLIC: int file_m3(SCR *, int); */ int file_m3(SCR *sp, int force) { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * Don't exit while in a temporary files if the file was ever modified. * The problem is that if the user does a ":wq", we write and quit, * unlinking the temporary file. Not what the user had in mind at all. * We permit writing to temporary files, so that user maps using file * system names work with temporary files. */ if (F_ISSET(sp->frp, FR_TMPEXIT) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "File is a temporary; exit will discard modifications"); return (1); } return (0); } /* * file_aw -- * Autowrite routine. If modified, autowrite is set and the readonly bit * is not set, write the file. A routine so there's a place to put the * comment. * * PUBLIC: int file_aw(SCR *, int); */ int file_aw(SCR *sp, int flags) { if (!F_ISSET(sp->ep, F_MODIFIED)) return (0); if (!O_ISSET(sp, O_AUTOWRITE)) return (0); /* * !!! * Historic 4BSD vi attempted to write the file if autowrite was set, * regardless of the writeability of the file (as defined by the file * readonly flag). System V changed this as some point, not attempting * autowrite if the file was readonly. This feels like a bug fix to * me (e.g. the principle of least surprise is violated if readonly is * set and vi writes the file), so I'm compatible with System V. */ if (O_ISSET(sp, O_READONLY)) { msgq(sp, M_INFO, "File readonly, modifications not auto-written"); return (1); } return (file_write(sp, NULL, NULL, NULL, flags)); } /* * set_alt_name -- * Set the alternate pathname. * * Set the alternate pathname. It's a routine because I wanted some place * to hang this comment. The alternate pathname (normally referenced using * the special character '#' during file expansion and in the vi ^^ command) * is set by almost all ex commands that take file names as arguments. The * rules go something like this: * * 1: If any ex command takes a file name as an argument (except for the * :next command), the alternate pathname is set to that file name. * This excludes the command ":e" and ":w !command" as no file name * was specified. Note, historically, the :source command did not set * the alternate pathname. It does in nvi, for consistency. * * 2: However, if any ex command sets the current pathname, e.g. the * ":e file" or ":rew" commands succeed, then the alternate pathname * is set to the previous file's current pathname, if it had one. * This includes the ":file" command and excludes the ":e" command. * So, by rule #1 and rule #2, if ":edit foo" fails, the alternate * pathname will be "foo", if it succeeds, the alternate pathname will * be the previous current pathname. The ":e" command will not set * the alternate or current pathnames regardless. * * 3: However, if it's a read or write command with a file argument and * the current pathname has not yet been set, the file name becomes * the current pathname, and the alternate pathname is unchanged. * * If the user edits a temporary file, there may be times when there is no * alternative file name. A name argument of NULL turns it off. * * PUBLIC: void set_alt_name(SCR *, char *); */ void set_alt_name(SCR *sp, char *name) { free(sp->alt_name); if (name == NULL) sp->alt_name = NULL; else if ((sp->alt_name = strdup(name)) == NULL) msgq(sp, M_SYSERR, NULL); } /* * file_lock -- * Get an exclusive lock on a file. * * PUBLIC: lockr_t file_lock(SCR *, char *, int *, int, int); */ lockr_t file_lock(SCR *sp, char *name, int *fdp, int fd, int iswrite) { if (!O_ISSET(sp, O_LOCKFILES)) return (LOCK_SUCCESS); /* Set close-on-exec flag so locks are not inherited by shell cmd. */ if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) msgq_str(sp, M_SYSERR, name, "%s"); /* * !!! * We need to distinguish a lock not being available for the file * from the file system not supporting locking. Flock is documented * as returning EWOULDBLOCK; add EAGAIN for good measure, and assume * they are the former. There's no portable way to do this. */ errno = 0; #ifdef __solaris__ return (LOCK_FAILED); #else return (flock(fd, LOCK_EX | LOCK_NB) ? errno == EAGAIN || errno == EWOULDBLOCK ? LOCK_UNAVAIL : LOCK_FAILED : LOCK_SUCCESS); #endif /* ifdef __solaris__ */ } ================================================ FILE: common/exf.h ================================================ /* $OpenBSD: exf.h,v 1.6 2022/02/20 19:45:51 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)exf.h 10.7 (Berkeley) 7/9/96 */ #ifdef _AIX # include "../include/compat.h" # include # include # include # include # undef open #endif /* ifdef _AIX */ /* * exf -- * The file structure. */ struct _exf { int refcnt; /* Reference count. */ /* Underlying database state. */ DB *db; /* File db structure. */ char *c_lp; /* Cached line. */ size_t c_len; /* Cached line length. */ recno_t c_lno; /* Cached line number. */ recno_t c_nlines; /* Cached lines in the file. */ DB *log; /* Log db structure. */ char *l_lp; /* Log buffer. */ size_t l_len; /* Log buffer length. */ recno_t l_high; /* Log last + 1 record number. */ recno_t l_cur; /* Log current record number. */ MARK l_cursor; /* Log cursor position. */ dir_t lundo; /* Last undo direction. */ LIST_HEAD(_markh, _lmark) marks;/* Linked list of file MARK's. */ dev_t mdev; /* Device. */ ino_t minode; /* Inode. */ #ifdef _AIX struct st_timespec mtim; /* Last modification time. (AIX 7+) */ #else struct timespec mtim; /* Last modification time. */ #endif /* ifdef _AIX */ int fcntl_fd; /* Fcntl locking fd; see exf.c. */ /* * Recovery in general, and these fields specifically, are described * in recover.c. */ #define RCV_PERIOD 120 /* Sync every two minutes. */ char *rcv_path; /* Recover file name. */ char *rcv_mpath; /* Recover mail file name. */ int rcv_fd; /* Locked mail file descriptor. */ #define F_DEVSET 0x001 /* mdev/minode fields initialized. */ #define F_FIRSTMODIFY 0x002 /* File not yet modified. */ #define F_MODIFIED 0x004 /* File is currently dirty. */ #define F_MULTILOCK 0x008 /* Multiple processes running, lock. */ #define F_NOLOG 0x010 /* Logging turned off. */ #define F_RCV_NORM 0x020 /* Don't delete recovery files. */ #define F_RCV_ON 0x040 /* Recovery is possible. */ #define F_UNDO 0x080 /* No change since last undo. */ #define F_RCV_SYNC 0x100 /* Recovery file sync needed. */ u_int16_t flags; }; /* Flags to db_get(). */ #define DBG_FATAL 0x001 /* If DNE, error message. */ #define DBG_NOCACHE 0x002 /* Ignore the front-end cache. */ /* Flags to file_init() and file_write(). */ #define FS_ALL 0x001 /* Write the entire file. */ #define FS_APPEND 0x002 /* Append to the file. */ #define FS_FORCE 0x004 /* Force is set. */ #define FS_OPENERR 0x008 /* Open failed, try it again. */ #define FS_POSSIBLE 0x010 /* Force could have been set. */ #define FS_SETALT 0x020 /* Set alternate file name. */ /* Flags to rcv_sync(). */ #define RCV_EMAIL 0x01 /* Send the user email, IFF file modified. */ #define RCV_ENDSESSION 0x02 /* End the file session. */ #define RCV_PRESERVE 0x04 /* Preserve backup file, IFF file modified. */ #define RCV_SNAPSHOT 0x08 /* Snapshot the recovery, and send email. */ ================================================ FILE: common/gs.h ================================================ /* $OpenBSD: gs.h,v 1.18 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)gs.h 10.34 (Berkeley) 9/24/96 */ #define TEMPORARY_FILE_STRING "/tmp" /* Default temporary file name. */ /* * File reference structure (FREF). The structure contains the name of the * file, along with the information that follows the name. * * !!! * The read-only bit follows the file name, not the file itself. */ struct _fref { TAILQ_ENTRY(_fref) q; /* Linked list of file references. */ char *name; /* File name. */ char *tname; /* Backing temporary file name. */ recno_t lno; /* 1-N: file cursor line. */ size_t cno; /* 0-N: file cursor column. */ #define FR_CURSORSET 0x0001 /* If lno/cno values valid. */ #define FR_DONTDELETE 0x0002 /* Don't delete the temporary file. */ #define FR_EXNAMED 0x0004 /* Read/write renamed the file. */ #define FR_NAMECHANGE 0x0008 /* If the name changed. */ #define FR_NEWFILE 0x0010 /* File doesn't really exist yet. */ #define FR_RECOVER 0x0020 /* File is being recovered. */ #define FR_TMPEXIT 0x0040 /* Modified temporary file, no exit. */ #define FR_TMPFILE 0x0080 /* If file has no name. */ #define FR_UNLOCKED 0x0100 /* File couldn't be locked. */ u_int16_t flags; }; /* Action arguments to scr_exadjust(). */ typedef enum { EX_TERM_CE, EX_TERM_SCROLL } exadj_t; /* Screen attribute arguments to scr_attr(). */ typedef enum { SA_ALTERNATE, SA_INVERSE } scr_attr_t; /* Input method control arguments to scr_imctrl(). */ typedef enum { IMCTRL_INIT, IMCTRL_OFF, IMCTRL_ON } imctrl_t; /* Key type arguments to scr_keyval(). */ typedef enum { KEY_VEOF, KEY_VERASE, KEY_VKILL, KEY_VWERASE } scr_keyval_t; /* * GS: * * Structure that describes global state of the running program. */ struct _gs { int id; /* Last allocated screen id. */ TAILQ_HEAD(_dqh, _scr) dq; /* Displayed screens. */ TAILQ_HEAD(_hqh, _scr) hq; /* Hidden screens. */ SCR *ccl_sp; /* Colon command-line screen. */ void *cl_private; /* Curses support private area. */ /* File references. */ TAILQ_HEAD(_frefh, _fref) frefq; #define GO_COLUMNS 0 /* Global options: columns. */ #define GO_LINES 1 /* Global options: lines. */ #define GO_SECURE 2 /* Global options: secure. */ #define GO_TERM 3 /* Global options: terminal type. */ OPTION opts[GO_TERM + 1]; MSGH msgq; /* User message list. */ #define DEFAULT_NOPRINT '\1' /* Emergency non-printable character */ CHAR_T noprint; /* Cached, unprintable character. */ char *tmp_bp; /* Temporary buffer. */ size_t tmp_blen; /* Temporary buffer size. */ /* * Ex command structures (EXCMD). Defined here because ex commands * exist outside of any particular screen or file. */ #define EXCMD_RUNNING(gp) (LIST_FIRST(&(gp)->ecq)->clen != 0) LIST_HEAD(_excmdh, _excmd) ecq; /* Ex command linked list. */ EXCMD excmd; /* Default ex command structure. */ char *if_name; /* Current associated file. */ recno_t if_lno; /* Current associated line number. */ char *c_option; /* Ex initial, command-line command. */ char *C_option; /* Ex initial, command-line command. */ #ifdef DEBUG FILE *tracefp; /* Trace file pointer. */ #endif /* ifdef DEBUG */ EVENT *i_event; /* Array of input events. */ size_t i_nelem; /* Number of array elements. */ size_t i_cnt; /* Count of events. */ size_t i_next; /* Offset of next event. */ CB *dcbp; /* Default cut buffer pointer. */ CB dcb_store; /* Default cut buffer storage. */ LIST_HEAD(_cuth, _cb) cutq; /* Linked list of cut buffers. */ #define MAX_BIT_SEQ 128 /* Max + 1 fast check character. */ LIST_HEAD(_seqh, _seq) seqq; /* Linked list of maps, abbrevs. */ bitstr_t bit_decl(seqb, MAX_BIT_SEQ); #define MAX_FAST_KEY 254 /* Max fast check character.*/ #define KEY_LEN(sp, ch) \ ((unsigned char)(ch) <= MAX_FAST_KEY ? \ (sp)->gp->cname[(unsigned char)(ch)].len : \ v_key_len((sp), (ch))) #define KEY_NAME(sp, ch) \ ((unsigned char)(ch) <= MAX_FAST_KEY ? \ (sp)->gp->cname[(unsigned char)(ch)].name : \ v_key_name((sp), (ch))) struct { CHAR_T name[MAX_CHARACTER_COLUMNS + 1]; u_int8_t len; } cname[MAX_FAST_KEY + 1]; /* Fast lookup table. */ #define KEY_VAL(sp, ch) \ ((unsigned char)(ch) <= MAX_FAST_KEY ? \ (sp)->gp->special_key[(unsigned char)(ch)] : \ (unsigned char)(ch) > (sp)->gp->max_special ? 0 : \ v_key_val((sp),(ch))) CHAR_T max_special; /* Max special character. */ unsigned char /* Fast lookup table. */ special_key[MAX_FAST_KEY + 1]; /* Flags. */ #define G_ABBREV 0x0001 /* If have abbreviations. */ #define G_BELLSCHED 0x0002 /* Bell scheduled. */ #define G_INTERRUPTED 0x0004 /* Interrupted. */ #define G_RECOVER_SET 0x0008 /* Recover system initialized. */ #define G_SCRIPTED 0x0010 /* Ex script session. */ #define G_SCRWIN 0x0020 /* Scripting windows running. */ #define G_SNAPSHOT 0x0040 /* Always snapshot files. */ #define G_SRESTART 0x0080 /* Screen restarted. */ #define G_TMP_INUSE 0x0100 /* Temporary buffer in use. */ u_int32_t flags; /* Screen interface functions... */ /* Add a string to the screen. */ int (*scr_addstr)(SCR *, const char *, size_t); /* Toggle a screen attribute. */ int (*scr_attr)(SCR *, scr_attr_t, int); /* Terminal baud rate. */ int (*scr_baud)(SCR *, unsigned long *); /* Beep/bell/flash the terminal. */ int (*scr_bell)(SCR *); /* Display a busy message. */ void (*scr_busy)(SCR *, const char *, busy_t); /* Clear to the end of the line. */ int (*scr_clrtoeol)(SCR *); /* Return the cursor location. */ int (*scr_cursor)(SCR *, size_t *, size_t *); /* Delete a line. */ int (*scr_deleteln)(SCR *); /* Get a keyboard event. */ int (*scr_event)(SCR *, EVENT *, u_int32_t, int); /* Ex: screen adjustment routine. */ int (*scr_ex_adjust)(SCR *, exadj_t); int (*scr_fmap) /* Set a function key. */ (SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t); /* Get terminal key value. */ int (*scr_keyval)(SCR *, scr_keyval_t, CHAR_T *, int *); /* Control the state of input method */ void (*scr_imctrl)(SCR *, imctrl_t); /* Insert a line. */ int (*scr_insertln)(SCR *); /* Handle an option change. */ int (*scr_optchange)(SCR *, int, char *, unsigned long *); /* Move the cursor. */ int (*scr_move)(SCR *, size_t, size_t); /* Message or ex output. */ void (*scr_msg)(SCR *, mtype_t, char *, size_t); /* Refresh the screen. */ int (*scr_refresh)(SCR *, int); /* Rename the file. */ int (*scr_rename)(SCR *, char *, int); /* Set the screen type. */ int (*scr_screen)(SCR *, u_int32_t); /* Suspend the editor. */ int (*scr_suspend)(SCR *, int *); /* Print usage message. */ void (*scr_usage)(void); }; ================================================ FILE: common/key.c ================================================ /* $OpenBSD: key.c,v 1.19 2022/04/21 17:50:50 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) static int v_event_append(SCR *, EVENT *); static int v_event_grow(SCR *, int); static int v_key_cmp(const void *, const void *); static void v_keyval(SCR *, int, scr_keyval_t); static void v_sync(SCR *, int); /* * !!! * Historic vi always used: * * ^D: autoindent deletion * ^H: last character deletion * ^W: last word deletion * ^Q: quote the next character (if not used in flow control). * ^V: quote the next character * * regardless of the user's choices for these characters. The user's erase * and kill characters worked in addition to these characters. Nvi wires * down the above characters, but in addition permits the VEOF, VERASE, VKILL * and VWERASE characters described by the user's termios structure. * * Ex was not consistent with this scheme, as it historically ran in tty * cooked mode. This meant that the scroll command and autoindent erase * characters were mapped to the user's EOF character, and the character * and word deletion characters were the user's tty character and word * deletion characters. This implementation makes it all consistent, as * described above for vi. * * !!! * This means that all screens share a special key set. */ KEYLIST keylist[] = { {K_BACKSLASH, '\\'}, /* \ */ {K_CARAT, '^'}, /* ^ */ {K_CNTRLD, '\004'}, /* ^D */ {K_CNTRLR, '\022'}, /* ^R */ {K_CNTRLT, '\024'}, /* ^T */ {K_CNTRLZ, '\032'}, /* ^Z */ {K_COLON, ':'}, /* : */ {K_CR, '\r'}, /* \r */ {K_ESCAPE, '\033'}, /* ^[ */ {K_FORMFEED, '\f'}, /* \f */ {K_HEXCHAR, '\030'}, /* ^X */ {K_NL, '\n'}, /* \n */ {K_RIGHTBRACE, '}'}, /* } */ {K_RIGHTPAREN, ')'}, /* ) */ {K_TAB, '\t'}, /* \t */ {K_VERASE, '\b'}, /* \b */ {K_VKILL, '\025'}, /* ^U */ {K_VLNEXT, '\021'}, /* ^Q */ {K_VLNEXT, '\026'}, /* ^V */ {K_VWERASE, '\027'}, /* ^W */ {K_ZERO, '0'}, /* 0 */ #define ADDITIONAL_CHARACTERS 4 {K_NOTUSED, 0}, /* VEOF, VERASE, VKILL, VWERASE */ {K_NOTUSED, 0}, {K_NOTUSED, 0}, {K_NOTUSED, 0}, }; static int nkeylist = (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS; /* * v_key_init -- * Initialize the special key lookup table. * * PUBLIC: int v_key_init(SCR *); */ int v_key_init(SCR *sp) { unsigned int ch; GS *gp; KEYLIST *kp; int cnt; gp = sp->gp; /* * XXX * 8-bit only, for now. Recompilation should get you any 8-bit * character set, as long as NULL isn't a character. */ (void)setlocale(LC_ALL, ""); (void)setlocale(LC_NUMERIC, ""); v_key_ilookup(sp); v_keyval(sp, K_CNTRLD, KEY_VEOF); v_keyval(sp, K_VERASE, KEY_VERASE); v_keyval(sp, K_VKILL, KEY_VKILL); v_keyval(sp, K_VWERASE, KEY_VWERASE); /* Sort the special key list. */ qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); /* Initialize the fast lookup table. */ for (gp->max_special = 0, kp = keylist, cnt = nkeylist; cnt--; ++kp) { if (gp->max_special < kp->value) gp->max_special = kp->value; if (kp->ch <= MAX_FAST_KEY) gp->special_key[kp->ch] = kp->value; } /* Find a non-printable character to use as a message separator. */ for (ch = 1; ch <= MAX_CHAR_T; ++ch) if (!isprint(ch)) { gp->noprint = ch; break; } if (ch != gp->noprint) { msgq(sp, M_ERR, "No non-printable character found"); return (1); } return (0); } /* * v_keyval -- * Set key values. * * We've left some open slots in the keylist table, and if these values exist, * we put them into place. Note, they may reset (or duplicate) values already * in the table, so we check for that first. */ static void v_keyval(SCR *sp, int val, scr_keyval_t name) { KEYLIST *kp; CHAR_T ch; int dne; /* Get the key's value from the screen. */ if (sp->gp->scr_keyval(sp, name, &ch, &dne)) return; if (dne) return; /* Check for duplication. */ for (kp = keylist; kp->value != K_NOTUSED; ++kp) if (kp->ch == ch) { kp->value = val; return; } /* Add a new entry. */ if (kp->value == K_NOTUSED) { keylist[nkeylist].ch = ch; keylist[nkeylist].value = val; ++nkeylist; } } /* * v_key_ilookup -- * Build the fast-lookup key display array. * * PUBLIC: void v_key_ilookup(SCR *); */ void v_key_ilookup(SCR *sp) { CHAR_T ch, *p, *t; GS *gp; size_t len; for (gp = sp->gp, ch = 0; ch <= MAX_FAST_KEY; ++ch) for (p = gp->cname[ch].name, t = v_key_name(sp, ch), len = gp->cname[ch].len = sp->clen; len--;) *p++ = *t++; } /* * v_key_len -- * Return the length of the string that will display the key. * This routine is the backup for the KEY_LEN() macro. * * PUBLIC: size_t v_key_len(SCR *, CHAR_T); */ size_t v_key_len(SCR *sp, CHAR_T ch) { (void)v_key_name(sp, ch); return (sp->clen); } /* * v_key_name -- * Return the string that will display the key. This routine * is the backup for the KEY_NAME() macro. * * PUBLIC: CHAR_T *v_key_name(SCR *, CHAR_T); */ CHAR_T * v_key_name(SCR *sp, CHAR_T ch) { static const CHAR_T hexdigit[] = "0123456789abcdef"; static const CHAR_T octdigit[] = "01234567"; CHAR_T *chp, mask; size_t len; int cnt, shift; /* See if the character was explicitly declared printable or not. */ if ((chp = O_STR(sp, O_PRINT)) != NULL) for (; *chp != '\0'; ++chp) if (*chp == ch) goto pr; if ((chp = O_STR(sp, O_NOPRINT)) != NULL) for (; *chp != '\0'; ++chp) if (*chp == ch) goto nopr; /* * Historical (ARPA standard) mappings. Printable characters are left * alone. Control characters less than 0x20 are represented as '^' * followed by the character offset from the '@' character in the ASCII * character set. Del (0x7f) is represented as '^' followed by '?'. * * If set O_ALTNOTATION, most control characters less than 0x20 are * displayed using notation. Carriage feed, escape, and * delete are displayed as , , and , respectively. * * XXX * The following code depends on the current locale being identical to * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f). I'm * told that this is a reasonable assumption... * * XXX * This code will only work with CHAR_T's that are multiples of 8-bit * bytes. * * XXX * NB: There's an assumption here that all printable characters take * up a single column on the screen. This is not always correct. */ if (isprint(ch)) { pr: sp->cname[0] = ch; len = 1; goto done; } nopr: if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) { if (O_ISSET(sp, O_ALTNOTATION)) { if (ch == 0x00) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = '@'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x01) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'a'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x02) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'b'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x03) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'c'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x04) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'd'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x05) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'e'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x06) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'f'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x07) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'g'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x08) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'h'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x0A) { sp->cname[0] = '<'; sp->cname[1] = 'N'; sp->cname[2] = 'L'; sp->cname[3] = '>'; len = 4; } else if (ch == 0x0B) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'k'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x0C) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'l'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x0D) { sp->cname[0] = '<'; sp->cname[1] = 'R'; sp->cname[2] = 'e'; sp->cname[3] = 't'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x0E) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'n'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x0F) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'o'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x10) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'p'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x11) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'q'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x12) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'r'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x13) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 's'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x14) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 't'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x15) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'u'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x16) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'v'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x17) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'w'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x18) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'x'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x19) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'y'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1A) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = 'z'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1B) { sp->cname[0] = '<'; sp->cname[1] = 'E'; sp->cname[2] = 's'; sp->cname[3] = 'c'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1C) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = '\\'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1D) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = ']'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1E) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = '^'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x1F) { sp->cname[0] = '<'; sp->cname[1] = 'C'; sp->cname[2] = '-'; sp->cname[3] = '_'; sp->cname[4] = '>'; len = 5; } else if (ch == 0x7F) { sp->cname[0] = '<'; sp->cname[1] = 'D'; sp->cname[2] = 'e'; sp->cname[3] = 'l'; sp->cname[4] = '>'; len = 5; } else { sp->cname[0] = '^'; sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; len = 2; } } else { sp->cname[0] = '^'; sp->cname[1] = ch == 0x7f ? '?' : '@' + ch; len = 2; } } else if (O_ISSET(sp, O_OCTAL)) { #define BITS (sizeof(CHAR_T) * 8) #define SHIFT (BITS - BITS % 3) #define TOPMASK (BITS % 3 == 2 ? 3 : 1) << (BITS - BITS % 3) sp->cname[0] = '\\'; sp->cname[1] = octdigit[(ch & TOPMASK) >> SHIFT]; shift = SHIFT - 3; for (len = 2, mask = 7 << (SHIFT - 3), cnt = BITS / 3; cnt-- > 0; mask >>= 3, shift -= 3) sp->cname[len++] = octdigit[(ch & mask) >> shift]; } else { sp->cname[0] = '\\'; sp->cname[1] = 'x'; for (len = 2, chp = (u_int8_t *)&ch, cnt = sizeof(CHAR_T); cnt-- > 0; ++chp) { sp->cname[len++] = hexdigit[(*chp & 0xf0) >> 4]; sp->cname[len++] = hexdigit[*chp & 0x0f]; } } done: sp->cname[sp->clen = len] = '\0'; return (sp->cname); } /* * v_key_val -- * Fill in the value for a key. This routine is the backup * for the KEY_VAL() macro. * * PUBLIC: int v_key_val(SCR *, CHAR_T); */ int v_key_val(SCR *sp, CHAR_T ch) { KEYLIST k, *kp; k.ch = ch; kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp); return (kp == NULL ? K_NOTUSED : kp->value); } /* * v_event_push -- * Push events/keys onto the front of the buffer. * * There is a single input buffer in ex/vi. Characters are put onto the * end of the buffer by the terminal input routines, and pushed onto the * front of the buffer by various other functions in ex/vi. Each key has * an associated flag value, which indicates if it has already been quoted, * and if it is the result of a mapping or an abbreviation. * * PUBLIC: int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, unsigned int); */ int v_event_push(SCR *sp, EVENT *p_evp, CHAR_T *p_s, size_t nitems, unsigned int flags) { EVENT *evp; GS *gp; size_t total; /* If we have room, stuff the items into the buffer. */ gp = sp->gp; if (nitems <= gp->i_next || (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) { if (gp->i_cnt != 0) gp->i_next -= nitems; goto copy; } /* * If there are currently items in the queue, shift them up, * leaving some extra room. Get enough space plus a little * extra. */ #define TERM_PUSH_SHIFT 30 total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT; if (total >= gp->i_nelem && v_event_grow(sp, MAXIMUM(total, 64))) return (1); if (gp == NULL) return (1); if (gp->i_cnt) MEMMOVE(gp->i_event + TERM_PUSH_SHIFT + nitems, gp->i_event + gp->i_next, gp->i_cnt); gp->i_next = TERM_PUSH_SHIFT; /* Put the new items into the queue. */ copy: gp->i_cnt += nitems; for (evp = gp->i_event + gp->i_next; nitems--; ++evp) { if (p_evp != NULL) *evp = *p_evp++; else { if (evp == NULL) return (1); evp->e_event = E_CHARACTER; evp->e_c = *p_s++; evp->e_value = KEY_VAL(sp, evp->e_c); F_INIT(&evp->e_ch, flags); } } return (0); } /* * v_event_append -- * Append events onto the tail of the buffer. */ static int v_event_append(SCR *sp, EVENT *argp) { CHAR_T *s; /* Characters. */ EVENT *evp; GS *gp; size_t nevents; /* Number of events. */ /* Grow the buffer as necessary. */ nevents = argp->e_event == E_STRING ? argp->e_len : 1; gp = sp->gp; if (gp->i_event == NULL || nevents > gp->i_nelem - (gp->i_next + gp->i_cnt)) v_event_grow(sp, MAXIMUM(nevents, 64)); evp = gp->i_event + gp->i_next + gp->i_cnt; gp->i_cnt += nevents; /* Transform strings of characters into single events. */ if (argp == NULL || evp == NULL) return (1); if (argp->e_event == E_STRING) for (s = argp->e_csp; nevents--; ++evp) { evp->e_event = E_CHARACTER; evp->e_c = *s++; evp->e_value = KEY_VAL(sp, evp->e_c); evp->e_flags = 0; } else *evp = *argp; return (0); } /* Remove events from the queue. */ #define QREM(len) { \ if ((gp->i_cnt -= (len)) == 0) \ gp->i_next = 0; \ else \ gp->i_next += (len); \ } /* * v_event_get -- * Return the next event. * * !!! * The flag EC_NODIGIT probably needs some explanation. First, the idea of * mapping keys is that one or more keystrokes act like a function key. * What's going on is that vi is reading a number, and the character following * the number may or may not be mapped (EC_MAPCOMMAND). For example, if the * user is entering the z command, a valid command is "z40+", and we don't want * to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it * into "z40xxx". However, if the user enters "35x", we want to put all of the * characters through the mapping code. * * Historical practice is a bit muddled here. (Surprise!) It always permitted * mapping digits as long as they weren't the first character of the map, e.g. * ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9 * (the digit 0 was a special case as it doesn't indicate the start of a count) * as the first character of the map, but then ignored those mappings. While * it's probably stupid to map digits, vi isn't your mother. * * The way this works is that the EC_MAPNODIGIT causes term_key to return the * end-of-digit without "looking" at the next character, i.e. leaving it as the * user entered it. Presumably, the next term_key call will tell us how the * user wants it handled. * * There is one more complication. Users might map keys to digits, and, as * it's described above, the commands: * * :map g 1G * d2g * * would return the keys "d21G", when the user probably wanted * "d21G". So, if a map starts off with a digit we continue as * before, otherwise, we pretend we haven't mapped the character, and return * . * * Now that that's out of the way, let's talk about Energizer Bunny macros. * It's easy to create macros that expand to a loop, e.g. map x 3x. It's * fairly easy to detect this example, because it's all internal to term_key. * If we're expanding a macro and it gets big enough, at some point we can * assume it's looping and kill it. The examples that are tough are the ones * where the parser is involved, e.g. map x "ayyx"byy. We do an expansion * on 'x', and get "ayyx"byy. We then return the first 4 characters, and then * find the looping macro again. There is no way that we can detect this * without doing a full parse of the command, because the character that might * cause the loop (in this case 'x') may be a literal character, e.g. the map * map x "ayy"xyy"byy is perfectly legal and won't cause a loop. * * Historic vi tried to detect looping macros by disallowing obvious cases in * the map command, maps that that ended with the same letter as they started * (which wrongly disallowed "map x 'x"), and detecting macros that expanded * too many times before keys were returned to the command parser. It didn't * get many (most?) of the tricky cases right, however, and it was certainly * possible to create macros that ran forever. And, even if it did figure out * what was going on, the user was usually tossed into ex mode. Finally, any * changes made before vi realized that the macro was recursing were left in * place. We recover gracefully, but the only recourse the user has in an * infinite macro loop is to interrupt. * * !!! * It is historic practice that mapping characters to themselves as the first * part of the mapped string was legal, and did not cause infinite loops, i.e. * ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching * characters were returned instead of being remapped. * * !!! * It is also historic practice that the macro "map ] ]]^" caused a single ] * keypress to behave as the command ]] (the ^ got the map past the vi check * for "tail recursion"). Conversely, the mapping "map n nn^" went recursive. * What happened was that, in the historic vi, maps were expanded as the keys * were retrieved, but not all at once and not centrally. So, the keypress ] * pushed ]]^ on the stack, and then the first ] from the stack was passed to * the ]] command code. The ]] command then retrieved a key without entering * the mapping code. This could bite us anytime a user has a map that depends * on secondary keys NOT being mapped. I can't see any possible way to make * this work in here without the complete abandonment of Rationality Itself. * * XXX * The final issue is recovery. It would be possible to undo all of the work * that was done by the macro if we entered a record into the log so that we * knew when the macro started, and, in fact, this might be worth doing at some * point. Given that this might make the log grow unacceptably (consider that * cursor keys are done with maps), for now we leave any changes made in place. * * PUBLIC: int v_event_get(SCR *, EVENT *, int, u_int32_t); */ int v_event_get(SCR *sp, EVENT *argp, int timeout, u_int32_t flags) { EVENT *evp, ev; GS *gp; SEQ *qp; int init_nomap, ispartial, istimeout, remap_cnt; gp = sp->gp; /* If simply checking for interrupts, argp may be NULL. */ if (argp == NULL) argp = &ev; retry: istimeout = remap_cnt = 0; /* * If the queue isn't empty and we're timing out for characters, * return immediately. */ if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT)) return (0); /* * If the queue is empty, we're checking for interrupts, or we're * timing out for characters, get more events. */ if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) { /* * If we're reading new characters, check any scripting * windows for input. */ if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp)) return (1); loop: if (gp->scr_event(sp, argp, LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout)) return (1); switch (argp->e_event) { case E_ERR: case E_SIGHUP: case E_SIGTERM: /* * Fatal conditions cause the file to be synced to * disk immediately. */ v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE | (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL)); return (1); case E_TIMEOUT: istimeout = 1; break; case E_INTERRUPT: /* Set the global interrupt flag. */ F_SET(sp->gp, G_INTERRUPTED); /* * If the caller was interested in interrupts, return * immediately. */ if (LF_ISSET(EC_INTERRUPT)) return (0); goto append; default: append: if (v_event_append(sp, argp)) return (1); break; } } /* * If the caller was only interested in interrupts or timeouts, return * immediately. (We may have gotten characters, and that's okay, they * were queued up for later use.) */ if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) return (0); newmap: evp = &gp->i_event[gp->i_next]; /* * If the next event in the queue isn't a character event, return * it, we're done. */ if (evp->e_event != E_CHARACTER) { *argp = *evp; QREM(1); return (0); } /* * If the key isn't mappable because: * * + ... the timeout has expired * + ... it's not a mappable key * + ... neither the command or input map flags are set * + ... there are no maps that can apply to it * * return it forthwith. */ if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) || !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) || (evp->e_c < MAX_BIT_SEQ && !bit_test(gp->seqb, evp->e_c))) goto nomap; /* Search the map. */ qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt, LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial); /* * If get a partial match, get more characters and retry the map. * If time out without further characters, return the characters * unmapped. * * !!! * characters are a problem. Cursor keys start with * characters, so there's almost always a map in place that begins with * an character. If we timeout keys in the same way * that we timeout other keys, the user will get a noticeable pause as * they enter to terminate input mode. If key timeout is set * for a slow link, users will get an even longer pause. Nvi used to * simply timeout characters at 1/10th of a second, but this * loses over PPP links where the latency is greater than 100Ms. */ if (ispartial) { if (O_ISSET(sp, O_TIMEOUT)) timeout = (evp->e_value == K_ESCAPE ? O_VAL(sp, O_ESCAPETIME) : O_VAL(sp, O_KEYTIME)) * 100; else timeout = 0; goto loop; } /* If no map, return the character. */ if (qp == NULL) { nomap: if (!isdigit(evp->e_c) && LF_ISSET(EC_MAPNODIGIT)) goto not_digit; *argp = *evp; QREM(1); return (0); } /* * If looking for the end of a digit string, and the first character * of the map is it, pretend we haven't seen the character. */ if (LF_ISSET(EC_MAPNODIGIT) && qp->output != NULL && !isdigit(qp->output[0])) { not_digit: argp->e_c = CH_NOT_DIGIT; argp->e_value = K_NOTUSED; argp->e_event = E_CHARACTER; F_INIT(&argp->e_ch, 0); return (0); } /* Find out if the initial segments are identical. */ if (qp->output != NULL) { init_nomap = !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen); } /* Delete the mapped characters from the queue. */ QREM(qp->ilen); /* If keys mapped to nothing, go get more. */ if (qp->output == NULL) goto retry; /* If remapping characters... */ if (O_ISSET(sp, O_REMAP)) { /* * Periodically check for interrupts. Always check the first * time through, because it's possible to set up a map that * will return a character every time, but will expand to more, * e.g. "map! a aaaa" will always return a 'a', but we'll never * get anywhere useful. */ if ((++remap_cnt == 1 || remap_cnt % 10 == 0) && (gp->scr_event(sp, &ev, EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) { F_SET(sp->gp, G_INTERRUPTED); argp->e_event = E_INTERRUPT; return (0); } /* * If an initial part of the characters mapped, they are not * further remapped -- return the first one. Push the rest * of the characters, or all of the characters if no initial * part mapped, back on the queue. */ if (init_nomap) { if (v_event_push(sp, NULL, qp->output + qp->ilen, qp->olen - qp->ilen, CH_MAPPED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->ilen, CH_NOMAP | CH_MAPPED)) return (1); evp = &gp->i_event[gp->i_next]; goto nomap; } if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED)) return (1); goto newmap; } /* Else, push the characters on the queue and return one. */ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP)) return (1); goto nomap; } /* * v_sync -- * Walk the screen lists, sync'ing files to their backup copies. */ static void v_sync(SCR *sp, int flags) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, &gp->dq, q) rcv_sync(sp, flags); TAILQ_FOREACH(sp, &gp->hq, q) rcv_sync(sp, flags); } /* * v_event_err -- * Unexpected event. * * PUBLIC: void v_event_err(SCR *, EVENT *); */ void v_event_err(SCR *sp, EVENT *evp) { switch (evp->e_event) { case E_CHARACTER: msgq(sp, M_ERR, "Unexpected character event"); break; case E_EOF: msgq(sp, M_ERR, "Unexpected end-of-file event"); break; case E_INTERRUPT: msgq(sp, M_ERR, "Unexpected interrupt event"); break; case E_QUIT: msgq(sp, M_ERR, "Unexpected quit event"); break; case E_REPAINT: msgq(sp, M_ERR, "Unexpected repaint event"); break; case E_STRING: msgq(sp, M_ERR, "Unexpected string event"); break; case E_TIMEOUT: msgq(sp, M_ERR, "Unexpected timeout event"); break; case E_WRESIZE: msgq(sp, M_ERR, "Unexpected resize event"); break; case E_WRITE: msgq(sp, M_ERR, "Unexpected write event"); break; /* * Theoretically, none of these can occur, as they're handled at the * top editor level. */ case E_ERR: case E_SIGHUP: case E_SIGTERM: default: abort(); } /* Free any allocated memory. */ free(evp->e_asp); } /* * v_event_flush -- * Flush any flagged keys, returning if any keys were flushed. * * PUBLIC: int v_event_flush(SCR *, unsigned int); */ int v_event_flush(SCR *sp, unsigned int flags) { GS *gp; int rval; for (rval = 0, gp = sp->gp; gp->i_cnt != 0 && F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1) QREM(1); return (rval); } /* * v_event_grow -- * Grow the terminal queue. */ static int v_event_grow(SCR *sp, int add) { GS *gp; size_t new_nelem, olen; gp = sp->gp; new_nelem = gp->i_nelem + add; olen = gp->i_nelem * sizeof(gp->i_event[0]); BINC_RET(sp, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0])); gp->i_nelem = olen / sizeof(gp->i_event[0]); return (0); } /* * v_key_cmp -- * Compare two keys for sorting. */ static int v_key_cmp(const void *ap, const void *bp) { return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch); } ================================================ FILE: common/key.h ================================================ /* $OpenBSD: key.h,v 1.8 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)key.h 10.18 (Berkeley) 6/30/96 */ /* * Fundamental character types. * * CHAR_T An integral type that can hold any character. * MAX_CHAR_T The maximum value of any character. * * If no integral type can hold a character, don't even try the port. */ typedef unsigned char CHAR_T; #define MAX_CHAR_T 0xff /* The maximum number of columns any character can take up on a screen. */ #define MAX_CHARACTER_COLUMNS 6 /* * Event types. * * The program structure depends on the event loop being able to return * E_EOF/E_ERR multiple times -- eventually enough things will end due * to the events that vi will reach the command level for the screen, at * which point the exit flags will be set and vi will exit. */ typedef enum { E_NOTUSED = 0, /* Not set. */ E_CHARACTER, /* Input character: e_c set. */ E_EOF, /* End of input (NOT ^D). */ E_ERR, /* Input error. */ E_INTERRUPT, /* Interrupt. */ E_QUIT, /* Quit. */ E_REPAINT, /* Repaint: e_flno, e_tlno set. */ E_SIGHUP, /* SIGHUP. */ E_SIGTERM, /* SIGTERM. */ E_STRING, /* Input string: e_csp, e_len set. */ E_TIMEOUT, /* Timeout. */ E_WRESIZE, /* Window resize. */ E_WRITE /* Write. */ } e_event_t; /* * Character values. */ typedef enum { K_NOTUSED = 0, /* Not set. */ K_BACKSLASH, /* \ */ K_CARAT, /* ^ */ K_CNTRLD, /* ^D */ K_CNTRLR, /* ^R */ K_CNTRLT, /* ^T */ K_CNTRLZ, /* ^Z */ K_COLON, /* : */ K_CR, /* \r */ K_ESCAPE, /* ^[ */ K_FORMFEED, /* \f */ K_HEXCHAR, /* ^X */ K_NL, /* \n */ K_RIGHTBRACE, /* } */ K_RIGHTPAREN, /* ) */ K_TAB, /* \t */ K_VERASE, /* set from tty: default ^H */ K_VKILL, /* set from tty: default ^U */ K_VLNEXT, /* set from tty: default ^V */ K_VWERASE, /* set from tty: default ^W */ K_ZERO /* 0 */ } e_key_t; struct _event { TAILQ_ENTRY(_event) q; /* Linked list of events. */ e_event_t e_event; /* Event type. */ union { struct { /* Input character. */ CHAR_T c; /* Character. */ e_key_t value; /* Key type. */ #define CH_ABBREVIATED 0x01 /* Character is from an abbreviation. */ #define CH_MAPPED 0x02 /* Character is from a map. */ #define CH_NOMAP 0x04 /* Do not map the character. */ #define CH_QUOTED 0x08 /* Character is already quoted. */ u_int8_t flags; } _e_ch; #define e_ch _u_event._e_ch /* !!! The structure, not the char. */ #define e_c _u_event._e_ch.c #define e_value _u_event._e_ch.value #define e_flags _u_event._e_ch.flags struct { /* Screen position, size. */ size_t lno1; /* Line number. */ size_t cno1; /* Column number. */ size_t lno2; /* Line number. */ size_t cno2; /* Column number. */ } _e_mark; #define e_lno _u_event._e_mark.lno1 /* Single location. */ #define e_cno _u_event._e_mark.cno1 #define e_flno _u_event._e_mark.lno1 /* Text region. */ #define e_fcno _u_event._e_mark.cno1 #define e_tlno _u_event._e_mark.lno2 #define e_tcno _u_event._e_mark.cno2 struct { /* Input string. */ CHAR_T *asp; /* Allocated string. */ CHAR_T *csp; /* String. */ size_t len; /* String length. */ } _e_str; #define e_asp _u_event._e_str.asp #define e_csp _u_event._e_str.csp #define e_len _u_event._e_str.len } _u_event; }; typedef struct _keylist { e_key_t value; /* Special value. */ CHAR_T ch; /* Key. */ } KEYLIST; extern KEYLIST keylist[]; /* Return if more keys in queue. */ #define KEYS_WAITING(sp) ((sp)->gp->i_cnt != 0) #define MAPPED_KEYS_WAITING(sp) \ (KEYS_WAITING(sp) && \ F_ISSET(&(sp)->gp->i_event[(sp)->gp->i_next].e_ch, CH_MAPPED)) /* The "standard" tab width, for displaying things to users. */ #define STANDARD_TAB 6 /* Various special characters, messages. */ #define CH_BSEARCH '?' /* Backward search prompt. */ #define CH_CURSOR ' ' /* Cursor character. */ #define CH_ENDMARK '$' /* End of a range. */ #define CH_EXPROMPT ':' /* Ex prompt. */ #define CH_FSEARCH '/' /* Forward search prompt. */ #define CH_HEX '\030' /* Leading hex character. */ #define CH_LITERAL '\026' /* ASCII ^V. */ #define CH_NO 'n' /* No. */ #define CH_NOT_DIGIT 'a' /* A non-isdigit() character. */ #define CH_QUIT 'q' /* Quit. */ #define CH_YES 'y' /* Yes. */ /* * Checking for interrupts means that we look at the bit that gets set if the * screen code supports asynchronous events, and call back into the event code * so that non-asynchronous screens get a chance to post the interrupt. * * INTERRUPT_CHECK is the number of lines "operated" on before checking for * interrupts. */ #define INTERRUPT_CHECK 100 #define INTERRUPTED(sp) \ (F_ISSET((sp)->gp, G_INTERRUPTED) || \ (!v_event_get((sp), NULL, 0, EC_INTERRUPT) && \ F_ISSET((sp)->gp, G_INTERRUPTED))) #define CLR_INTERRUPT(sp) \ F_CLR((sp)->gp, G_INTERRUPTED) /* Flags describing types of characters being requested. */ #define EC_INTERRUPT 0x001 /* Checking for interrupts. */ #define EC_MAPCOMMAND 0x002 /* Apply the command map. */ #define EC_MAPINPUT 0x004 /* Apply the input map. */ #define EC_MAPNODIGIT 0x008 /* Return to a digit. */ #define EC_QUOTED 0x010 /* Try to quote next character */ #define EC_RAW 0x020 /* Any next character. XXX: not used. */ #define EC_TIMEOUT 0x040 /* Timeout to next character. */ /* Flags describing text input special cases. */ #define TXT_ADDNEWLINE 0x00000001 /* Replay starts on a new line. */ #define TXT_AICHARS 0x00000002 /* Leading autoindent chars. */ #define TXT_ALTWERASE 0x00000004 /* Option: altwerase. */ #define TXT_APPENDEOL 0x00000008 /* Appending after EOL. */ #define TXT_AUTOINDENT 0x00000010 /* Autoindent set this line. */ #define TXT_BACKSLASH 0x00000020 /* Backslashes escape characters. */ #define TXT_BEAUTIFY 0x00000040 /* Only printable characters. */ #define TXT_BS 0x00000080 /* Backspace returns the buffer. */ #define TXT_CEDIT 0x00000100 /* Can return TERM_CEDIT. */ #define TXT_CNTRLD 0x00000200 /* Control-D is a command. */ #define TXT_CNTRLT 0x00000400 /* Control-T is an indent special. */ #define TXT_CR 0x00000800 /* CR returns the buffer. */ #define TXT_DOTTERM 0x00001000 /* Leading '.' terminates the input. */ #define TXT_EMARK 0x00002000 /* End of replacement mark. */ #define TXT_EOFCHAR 0x00004000 /* ICANON set, return EOF character. */ #define TXT_ESCAPE 0x00008000 /* Escape returns the buffer. */ #define TXT_FILEC 0x00010000 /* Option: filec. */ #define TXT_INFOLINE 0x00020000 /* Editing the info line. */ #define TXT_MAPINPUT 0x00040000 /* Apply the input map. */ #define TXT_NLECHO 0x00080000 /* Echo the newline. */ #define TXT_NUMBER 0x00100000 /* Number the line. */ #define TXT_OVERWRITE 0x00200000 /* Overwrite characters. */ #define TXT_PROMPT 0x00400000 /* Display a prompt. */ #define TXT_RECORD 0x00800000 /* Record for replay. */ #define TXT_REPLACE 0x01000000 /* Replace; don't delete overwrite. */ #define TXT_REPLAY 0x02000000 /* Replay the last input. */ #define TXT_RESOLVE 0x04000000 /* Resolve the text into the file. */ #define TXT_SEARCHINCR 0x08000000 /* Incremental search. */ #define TXT_SHOWMATCH 0x10000000 /* Option: showmatch. */ #define TXT_TTYWERASE 0x20000000 /* Option: ttywerase. */ #define TXT_WRAPMARGIN 0x40000000 /* Option: wrapmargin. */ ================================================ FILE: common/line.c ================================================ /* $OpenBSD: line.c,v 1.17 2025/07/30 22:19:13 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" static int scr_update(SCR *, recno_t, lnop_t, int); /* * db_eget -- * Front-end to db_get, special case handling for empty files. * * PUBLIC: int db_eget(SCR *, recno_t, char **, size_t *, int *); */ int db_eget(SCR *sp, recno_t lno, char **pp, size_t *lenp, int *isemptyp) { recno_t l1; if (isemptyp != NULL) *isemptyp = 0; /* If the line exists, simply return it. */ if (!db_get(sp, lno, 0, pp, lenp)) return (0); /* * If the user asked for line 0 or line 1, i.e. the only possible * line in an empty file, find the last line of the file; db_last * fails loudly. */ if ((lno == OOBLNO || lno == 1) && db_last(sp, &l1)) return (1); /* If the file isn't empty, fail loudly. */ if ((lno != 0 && lno != 1) || l1 != 0) { db_err(sp, lno); return (1); } if (isemptyp != NULL) *isemptyp = 1; return (1); } /* * db_get -- * Look in the text buffers for a line, followed by the cache, followed * by the database. * * PUBLIC: int db_get(SCR *, recno_t, u_int32_t, char **, size_t *); */ int db_get(SCR *sp, recno_t lno, u_int32_t flags, char **pp, size_t *lenp) { DBT data, key; EXF *ep; TEXT *tp; recno_t l1, l2; /* * The underlying recno stuff handles zero by returning NULL, but * have to have an OOB condition for the look-aside into the input * buffer anyway. */ if (lno == OOBLNO) goto err1; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); goto err3; } if (LF_ISSET(DBG_NOCACHE)) goto nocache; /* * Look-aside into the TEXT buffers and see if the line we want * is there. */ if (F_ISSET(sp, SC_TINPUT)) { l1 = TAILQ_FIRST(&sp->tiq)->lno; l2 = TAILQ_LAST(&sp->tiq, _texth)->lno; if (l1 <= lno && l2 >= lno) { TAILQ_FOREACH(tp, &sp->tiq, q) { if (tp->lno == lno) break; } if (lenp != NULL) *lenp = tp->len; if (pp != NULL) *pp = tp->lb; return (0); } /* * Adjust the line number for the number of lines used * by the text input buffers. */ if (lno > l2) lno -= l2 - l1; } /* Look-aside into the cache, and see if the line we want is there. */ if (lno == ep->c_lno) { if (lenp != NULL) *lenp = ep->c_len; if (pp != NULL) *pp = ep->c_lp; return (0); } ep->c_lno = OOBLNO; nocache: /* Get the line from the underlying database. */ key.data = &lno; key.size = sizeof(lno); switch (ep->db->get(ep->db, &key, &data, 0)) { case -1: goto err2; case 1: err1: if (LF_ISSET(DBG_FATAL)) err2: db_err(sp, lno); err3: if (lenp != NULL) *lenp = 0; if (pp != NULL) *pp = NULL; return (1); } /* Reset the cache. */ ep->c_lno = lno; ep->c_len = data.size; ep->c_lp = data.data; if (lenp != NULL) *lenp = data.size; if (pp != NULL) *pp = ep->c_lp; return (0); } /* * db_delete -- * Delete a line from the file. * * PUBLIC: int db_delete(SCR *, recno_t); */ int db_delete(SCR *sp, recno_t lno) { DBT key; EXF *ep; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Update marks, @ and global commands. */ if (mark_insdel(sp, LINE_DELETE, lno)) return (1); if (ex_g_insdel(sp, LINE_DELETE, lno)) return (1); /* Log change. */ log_line(sp, lno, LOG_LINE_DELETE); /* Update file. */ key.data = &lno; key.size = sizeof(lno); if (ep->db->del(ep->db, &key, 0) == 1) { msgq(sp, M_SYSERR, "unable to delete line %'lu", (unsigned long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno <= ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) --ep->c_nlines; /* File now modified. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED | F_RCV_SYNC); /* Update screen. */ return (scr_update(sp, lno, LINE_DELETE, 1)); } /* * db_append -- * Append a line into the file. * * PUBLIC: int db_append(SCR *, int, recno_t, char *, size_t); */ int db_append(SCR *sp, int update, recno_t lno, char *p, size_t len) { DBT data, key; EXF *ep; int rval; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = p; data.size = len; if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) { msgq(sp, M_SYSERR, "unable to append to line %'lu", (unsigned long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno < ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) ++ep->c_nlines; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED | F_RCV_SYNC); /* Log change. */ log_line(sp, lno + 1, LOG_LINE_APPEND); /* Update marks, @ and global commands. */ rval = 0; if (mark_insdel(sp, LINE_INSERT, lno + 1)) rval = 1; if (ex_g_insdel(sp, LINE_INSERT, lno + 1)) rval = 1; /* * Update screen. * * XXX * Nasty hack. If multiple lines are input by the user, they aren't * committed until an is entered. The problem is the screen was * updated/scrolled as each line was entered. So, when this routine * is called to copy the new lines from the cut buffer into the file, * it has to know not to update the screen again. */ return (scr_update(sp, lno, LINE_APPEND, update) || rval); } /* * db_insert -- * Insert a line into the file. * * PUBLIC: int db_insert(SCR *, recno_t, char *, size_t); */ int db_insert(SCR *sp, recno_t lno, char *p, size_t len) { DBT data, key; EXF *ep; int rval; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = p; data.size = len; if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) { msgq(sp, M_SYSERR, "unable to insert at line %'lu", (unsigned long)lno); return (1); } /* Flush the cache, update line count, before screen update. */ if (lno >= ep->c_lno) ep->c_lno = OOBLNO; if (ep->c_nlines != OOBLNO) ++ep->c_nlines; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED | F_RCV_SYNC); /* Log change. */ log_line(sp, lno, LOG_LINE_INSERT); /* Update marks, @ and global commands. */ rval = 0; if (mark_insdel(sp, LINE_INSERT, lno)) rval = 1; if (ex_g_insdel(sp, LINE_INSERT, lno)) rval = 1; /* Update screen. */ return (scr_update(sp, lno, LINE_INSERT, 1) || rval); } /* * db_set -- * Store a line in the file. * * PUBLIC: int db_set(SCR *, recno_t, char *, size_t); */ int db_set(SCR *sp, recno_t lno, char *p, size_t len) { DBT data, key; EXF *ep; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* Log before change. */ log_line(sp, lno, LOG_LINE_RESET_B); /* Update file. */ key.data = &lno; key.size = sizeof(lno); data.data = p; data.size = len; if (ep->db->put(ep->db, &key, &data, 0) == -1) { msgq(sp, M_SYSERR, "unable to store line %'lu", (unsigned long)lno); return (1); } /* Flush the cache, before logging or screen update. */ if (lno == ep->c_lno) ep->c_lno = OOBLNO; /* File now dirty. */ if (F_ISSET(ep, F_FIRSTMODIFY)) (void)rcv_init(sp); F_SET(ep, F_MODIFIED | F_RCV_SYNC); /* Log after change. */ log_line(sp, lno, LOG_LINE_RESET_F); /* Update screen. */ return (scr_update(sp, lno, LINE_RESET, 1)); } /* * db_exist -- * Return if a line exists. * * PUBLIC: int db_exist(SCR *, recno_t); */ int db_exist(SCR *sp, recno_t lno) { EXF *ep; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } if (lno == OOBLNO) return (0); /* * Check the last-line number cache. Adjust the cached line * number for the lines used by the text input buffers. */ if (ep->c_nlines != OOBLNO) return (lno <= (F_ISSET(sp, SC_TINPUT) ? ep->c_nlines + (TAILQ_LAST(&sp->tiq, _texth)->lno - TAILQ_FIRST(&sp->tiq)->lno) : ep->c_nlines)); /* Go get the line. */ return (!db_get(sp, lno, 0, NULL, NULL)); } /* * db_last -- * Return the number of lines in the file. * * PUBLIC: int db_last(SCR *, recno_t *); */ int db_last(SCR *sp, recno_t *lnop) { DBT data, key; EXF *ep; recno_t lno; /* Check for no underlying file. */ if ((ep = sp->ep) == NULL) { ex_emsg(sp, NULL, EXM_NOFILEYET); return (1); } /* * Check the last-line number cache. Adjust the cached line * number for the lines used by the text input buffers. */ if (ep->c_nlines != OOBLNO) { *lnop = ep->c_nlines; if (F_ISSET(sp, SC_TINPUT)) *lnop += TAILQ_LAST(&sp->tiq, _texth)->lno - TAILQ_FIRST(&sp->tiq)->lno; return (0); } key.data = &lno; key.size = sizeof(lno); switch (ep->db->seq(ep->db, &key, &data, R_LAST)) { case -1: msgq(sp, M_SYSERR, "unable to get last line"); *lnop = 0; return (1); case 1: *lnop = 0; return (0); default: break; } /* Fill the cache. */ memcpy(&lno, key.data, sizeof(lno)); ep->c_nlines = ep->c_lno = lno; ep->c_len = data.size; ep->c_lp = data.data; /* Return the value. */ *lnop = (F_ISSET(sp, SC_TINPUT) && TAILQ_LAST(&sp->tiq, _texth)->lno > lno ? TAILQ_LAST(&sp->tiq, _texth)->lno : lno); return (0); } /* * db_err -- * Report a line error. * * PUBLIC: void db_err(SCR *, recno_t); */ void db_err(SCR *sp, recno_t lno) { if (lno) msgq(sp, M_ERR, "Error: unable to retrieve line %'lu", (unsigned long)lno); } /* * scr_update -- * Update all of the screens that are backed by the file that * just changed. */ static int scr_update(SCR *sp, recno_t lno, lnop_t op, int current) { EXF *ep; SCR *tsp; if (F_ISSET(sp, SC_EX)) return (0); ep = sp->ep; if (ep->refcnt != 1) TAILQ_FOREACH(tsp, &sp->gp->dq, q) if (sp != tsp && tsp->ep == ep) if (vs_change(tsp, lno, op)) return (1); return (current ? vs_change(sp, lno, op) : 0); } ================================================ FILE: common/log.c ================================================ /* $OpenBSD: log.c,v 1.12 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #undef open /* * The log consists of records, each containing a type byte and a variable * length byte string, as follows: * * LOG_CURSOR_INIT MARK * LOG_CURSOR_END MARK * LOG_LINE_APPEND recno_t char * * LOG_LINE_DELETE recno_t char * * LOG_LINE_INSERT recno_t char * * LOG_LINE_RESET_F recno_t char * * LOG_LINE_RESET_B recno_t char * * LOG_MARK LMARK * * We do before image physical logging. This means that the editor layer * MAY NOT modify records in place, even if simply deleting or overwriting * characters. Since the smallest unit of logging is a line, we're using * up lots of space. This may eventually have to be reduced, probably by * doing logical logging, which is a much cooler database phrase. * * The implementation of the historic vi 'u' command, using roll-forward and * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record, * followed by a number of other records, followed by a LOG_CURSOR_END record. * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B * record, and is the line before the change. The second is LOG_LINE_RESET_F, * and is the line after the change. Roll-back is done by backing up to the * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a * similar fashion. * * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END * record for a line different from the current one. It should be noted that * this means that a subsequent 'u' command will make a change based on the * new position of the log's cursor. This is okay, and, in fact, historic vi * behaved that way. */ static int log_cursor1(SCR *, int); static void log_err(SCR *, char *, int); /* Try and restart the log on failure, i.e. if we run out of memory. */ #define LOG_ERR { \ log_err(sp, __FILE__, __LINE__); \ return (1); \ } /* * log_init -- * Initialize the logging subsystem. * * PUBLIC: int log_init(SCR *, EXF *); */ int log_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Initialize the buffer. The logging subsystem has its own * buffers because the global ones are almost by definition * going to be in use when the log runs. */ ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR, S_IRUSR | S_IWUSR, DB_RECNO, NULL); if (ep->log == NULL) { msgq(sp, M_SYSERR, "Log file"); F_SET(ep, F_NOLOG); return (1); } return (0); } /* * log_end -- * Close the logging subsystem. * * PUBLIC: int log_end(SCR *, EXF *); */ int log_end(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ if (ep->log != NULL) { (void)(ep->log->close)(ep->log); ep->log = NULL; } free(ep->l_lp); ep->l_lp = NULL; ep->l_len = 0; ep->l_cursor.lno = 1; /* XXX Any valid recno. */ ep->l_cursor.cno = 0; ep->l_high = ep->l_cur = 1; return (0); } /* * log_cursor -- * Log the current cursor position, starting an event. * * PUBLIC: int log_cursor(SCR *); */ int log_cursor(SCR *sp) { EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * If any changes were made since the last cursor init, * put out the ending cursor record. */ if (ep->l_cursor.lno == OOBLNO) { ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (log_cursor1(sp, LOG_CURSOR_END)); } ep->l_cursor.lno = sp->lno; ep->l_cursor.cno = sp->cno; return (0); } /* * log_cursor1 -- * Actually push a cursor record out. */ static int log_cursor1(SCR *sp, int type) { DBT data, key; EXF *ep; ep = sp->ep; BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(unsigned char) + sizeof(MARK)); ep->l_lp[0] = type; memmove(ep->l_lp + sizeof(unsigned char), &ep->l_cursor, sizeof(MARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(unsigned char) + sizeof(MARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_line -- * Log a line change. * * PUBLIC: int log_line(SCR *, recno_t, unsigned int); */ int log_line(SCR *sp, recno_t lno, unsigned int action) { DBT data, key; EXF *ep; size_t len; char *lp; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* * XXX * * Kluge for vi. Clear the EXF undo flag so that the * next 'u' command does a roll-back, regardless. */ F_CLR(ep, F_UNDO); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } /* * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a * special case, avoid the caches. Also, if it fails and it's * line 1, it just means that the user started with an empty file, * so fake an empty length line. */ if (action == LOG_LINE_RESET_B) { if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) { if (lno != 1) { db_err(sp, lno); return (1); } len = 0; lp = ""; } } else if (db_get(sp, lno, DBG_FATAL, &lp, &len)) return (1); BINC_RET(sp, ep->l_lp, ep->l_len, len + sizeof(unsigned char) + sizeof(recno_t)); ep->l_lp[0] = action; memmove(ep->l_lp + sizeof(unsigned char), &lno, sizeof(recno_t)); memmove(ep->l_lp + sizeof(unsigned char) + sizeof(recno_t), lp, len); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = len + sizeof(unsigned char) + sizeof(recno_t); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * log_mark -- * Log a mark position. For the log to work, we assume that there * aren't any operations that just put out a log record -- this * would mean that undo operations would only reset marks, and not * cause any other change. * * PUBLIC: int log_mark(SCR *, LMARK *); */ int log_mark(SCR *sp, LMARK *lmp) { DBT data, key; EXF *ep; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) return (0); /* Put out one initial cursor record per set of changes. */ if (ep->l_cursor.lno != OOBLNO) { if (log_cursor1(sp, LOG_CURSOR_INIT)) return (1); ep->l_cursor.lno = OOBLNO; } BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(unsigned char) + sizeof(LMARK)); ep->l_lp[0] = LOG_MARK; memmove(ep->l_lp + sizeof(unsigned char), lmp, sizeof(LMARK)); key.data = &ep->l_cur; key.size = sizeof(recno_t); data.data = ep->l_lp; data.size = sizeof(unsigned char) + sizeof(LMARK); if (ep->log->put(ep->log, &key, &data, 0) == -1) LOG_ERR; /* Reset high water mark. */ ep->l_high = ++ep->l_cur; return (0); } /* * Log_backward -- * Roll the log backward one operation. * * PUBLIC: int log_backward(SCR *, MARK *); */ int log_backward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; unsigned char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) { msgq(sp, M_BERR, "No changes to undo"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; switch (*(p = (unsigned char *)data.data)) { case LOG_CURSOR_INIT: if (didop) { memmove(rp, p + sizeof(unsigned char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_insert(sp, lno, p + sizeof(unsigned char) + sizeof(recno_t), data.size - sizeof(unsigned char) - sizeof(recno_t))) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_set(sp, lno, p + sizeof(unsigned char) + sizeof(recno_t), data.size - sizeof(unsigned char) - sizeof(recno_t))) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_setline -- * Reset the line to its original appearance. * * XXX * There's a bug in this code due to our not logging cursor movements * unless a change was made. If you do a change, move off the line, * then move back on and do a 'U', the line will be restored to the way * it was before the original change. * * PUBLIC: int log_setline(SCR *); */ int log_setline(SCR *sp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; unsigned char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "Logging not being performed, undo not possible"); return (1); } if (ep->l_cur == 1) return (1); F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (;;) { --ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; switch (*(p = (unsigned char *)data.data)) { case LOG_CURSOR_INIT: memmove(&m, p + sizeof(unsigned char), sizeof(MARK)); if (m.lno != sp->lno || ep->l_cur == 1) { F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_END: memmove(&m, p + sizeof(unsigned char), sizeof(MARK)); if (m.lno != sp->lno) { ++ep->l_cur; F_CLR(ep, F_NOLOG); return (0); } break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: case LOG_LINE_DELETE: case LOG_LINE_RESET_F: break; case LOG_LINE_RESET_B: memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (lno == sp->lno && db_set(sp, lno, p + sizeof(unsigned char) + sizeof(recno_t), data.size - sizeof(unsigned char) - sizeof(recno_t))) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * Log_forward -- * Roll the log forward one operation. * * PUBLIC: int log_forward(SCR *, MARK *); */ int log_forward(SCR *sp, MARK *rp) { DBT key, data; EXF *ep; LMARK lm; MARK m; recno_t lno; int didop; unsigned char *p; ep = sp->ep; if (F_ISSET(ep, F_NOLOG)) { msgq(sp, M_ERR, "Logging not being performed, roll-forward not possible"); return (1); } if (ep->l_cur == ep->l_high) { msgq(sp, M_BERR, "No changes to re-do"); return (1); } F_SET(ep, F_NOLOG); /* Turn off logging. */ key.data = &ep->l_cur; /* Initialize db request. */ key.size = sizeof(recno_t); for (didop = 0;;) { ++ep->l_cur; if (ep->log->get(ep->log, &key, &data, 0)) LOG_ERR; switch (*(p = (unsigned char *)data.data)) { case LOG_CURSOR_END: if (didop) { ++ep->l_cur; memmove(rp, p + sizeof(unsigned char), sizeof(MARK)); F_CLR(ep, F_NOLOG); return (0); } break; case LOG_CURSOR_INIT: break; case LOG_LINE_APPEND: case LOG_LINE_INSERT: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_insert(sp, lno, p + sizeof(unsigned char) + sizeof(recno_t), data.size - sizeof(unsigned char) - sizeof(recno_t))) goto err; ++sp->rptlines[L_ADDED]; break; case LOG_LINE_DELETE: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_delete(sp, lno)) goto err; ++sp->rptlines[L_DELETED]; break; case LOG_LINE_RESET_B: break; case LOG_LINE_RESET_F: didop = 1; memmove(&lno, p + sizeof(unsigned char), sizeof(recno_t)); if (db_set(sp, lno, p + sizeof(unsigned char) + sizeof(recno_t), data.size - sizeof(unsigned char) - sizeof(recno_t))) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } break; case LOG_MARK: didop = 1; memmove(&lm, p + sizeof(unsigned char), sizeof(LMARK)); m.lno = lm.lno; m.cno = lm.cno; if (mark_set(sp, lm.name, &m, 0)) goto err; break; default: abort(); } } err: F_CLR(ep, F_NOLOG); return (1); } /* * log_err -- * Try and restart the log on failure, i.e. if we run out of memory. */ static void log_err(SCR *sp, char *file, int line) { EXF *ep; msgq(sp, M_SYSERR, "%s/%d: log put error", openbsd_basename(file), line); ep = sp->ep; (void)ep->log->close(ep->log); if (!log_init(sp, ep)) msgq(sp, M_ERR, "Log restarted"); } ================================================ FILE: common/log.h ================================================ /* $OpenBSD: log.h,v 1.3 2001/01/29 01:58:30 niklas Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)log.h 10.2 (Berkeley) 3/6/96 */ #define LOG_NOTYPE 0 #define LOG_CURSOR_INIT 1 #define LOG_CURSOR_END 2 #define LOG_LINE_APPEND 3 #define LOG_LINE_DELETE 4 #define LOG_LINE_INSERT 5 #define LOG_LINE_RESET_F 6 #define LOG_LINE_RESET_B 7 #define LOG_MARK 8 ================================================ FILE: common/main.c ================================================ /* $OpenBSD: main.c,v 1.43 2021/10/24 21:24:17 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "errc.h" #include "common.h" #include "../vi/vi.h" #undef open #ifdef DEBUG static void attach(GS *); #endif /* ifdef DEBUG */ static int v_obsolete(char *[]); enum pmode pmode; /* * editor -- * Main editor routine. * * PUBLIC: int editor(GS *, int, char *[]); */ int editor(GS *gp, int argc, char *argv[]) { extern int openbsd_optind; extern char *openbsd_optarg; const char *p; EVENT ev; FREF *frp; SCR *sp; size_t len; unsigned int flags; int ch, flagchk, secure, startup, readonly, rval, silent; char *tag_f, *wsizearg, path[256]; static const char *optstr[3] = { #ifdef DEBUG "c:C:D:FlRrSsT:t:vw:", "c:C:D:eFlRrST:t:w:", "c:C:D:eFlrST:t:w:" #else "c:C:FlRrSst:vw:", "c:C:eFlRrSt:w:", "c:C:eFlrSt:w:" #endif /* ifdef DEBUG */ }; if (openbsd_pledge( "stdio rpath wpath cpath fattr flock getpw tty proc exec", NULL) == -1) { perror("pledge"); goto err; } /* Initialize the busy routine, if not defined by the screen. */ if (gp->scr_busy == NULL) gp->scr_busy = vs_busy; /* Initialize the message routine, if not defined by the screen. */ if (gp->scr_msg == NULL) gp->scr_msg = vs_msg; /* Common global structure initialization. */ TAILQ_INIT(&gp->dq); TAILQ_INIT(&gp->hq); LIST_INIT(&gp->ecq); LIST_INSERT_HEAD(&gp->ecq, &gp->excmd, q); gp->noprint = DEFAULT_NOPRINT; /* Structures shared by screens so stored in the GS structure. */ TAILQ_INIT(&gp->frefq); TAILQ_INIT(&gp->dcb_store.textq); LIST_INIT(&gp->cutq); LIST_INIT(&gp->seqq); /* Set initial screen type and mode based on the program name. */ readonly = 0; if (!strcmp(bsd_getprogname(), "ex") || !strcmp(bsd_getprogname(), "nex") || !strcmp(bsd_getprogname(), "oex") || !strcmp(bsd_getprogname(), "obex") || !strcmp(bsd_getprogname(), "openex")) LF_INIT(SC_EX); else { /* Nview, view are readonly. */ if (!strcmp(bsd_getprogname(), "view") || !strcmp(bsd_getprogname(), "nview") || !strcmp(bsd_getprogname(), "oview") || !strcmp(bsd_getprogname(), "obview") || !strcmp(bsd_getprogname(), "openview")) readonly = 1; /* Vi is the default. */ LF_INIT(SC_VI); } /* Convert old-style arguments into new-style ones. */ if (v_obsolete(argv)) return (1); /* Parse the arguments. */ flagchk = '\0'; tag_f = wsizearg = NULL; secure = silent = 0; startup = 1; /* Set the file snapshot flag. */ F_SET(gp, G_SNAPSHOT); pmode = MODE_EX; if (!strcmp(bsd_getprogname(), "ex") || !strcmp(bsd_getprogname(), "nex") || !strcmp(bsd_getprogname(), "oex") || !strcmp(bsd_getprogname(), "obex") || !strcmp(bsd_getprogname(), "openex")) pmode = MODE_EX; else if (!strcmp(bsd_getprogname(), "vi") || !strcmp(bsd_getprogname(), "nvi") || !strcmp(bsd_getprogname(), "ovi") || !strcmp(bsd_getprogname(), "obvi") || !strcmp(bsd_getprogname(), "openvi")) pmode = MODE_VI; else if (!strcmp(bsd_getprogname(), "view") || !strcmp(bsd_getprogname(), "nview") || !strcmp(bsd_getprogname(), "oview") || !strcmp(bsd_getprogname(), "obview") || !strcmp(bsd_getprogname(), "openview")) pmode = MODE_VIEW; while ((ch = openbsd_getopt(argc, argv, optstr[pmode])) != -1) switch (ch) { case 'C': /* Run the command. */ /* * XXX * Should we support multiple -C options? */ if (gp->c_option != NULL || gp->C_option != NULL) { openbsd_warnx("only one -c or -C command may be specified."); return (1); } gp->C_option = openbsd_optarg; break; case 'c': /* Run the command. */ /* * XXX * We should support multiple -c options. */ if (gp->C_option != NULL || gp->c_option != NULL) { openbsd_warnx("only one -c or -C command may be specified."); return (1); } gp->c_option = openbsd_optarg; break; #ifdef DEBUG case 'D': switch (openbsd_optarg[0]) { case 's': startup = 0; break; case 'w': attach(gp); break; default: openbsd_warnx("-D requires s or w argument."); return (1); } break; #endif /* ifdef DEBUG */ case 'e': /* Ex mode. */ LF_CLR(SC_VI); LF_SET(SC_EX); break; case 'F': /* No snapshot. */ F_CLR(gp, G_SNAPSHOT); break; case 'R': /* Readonly. */ readonly = 1; break; case 'r': /* Recover. */ if (flagchk == 't') { openbsd_warnx( "only one of -r and -t may be specified."); return (1); } flagchk = 'r'; break; case 'S': secure = 1; break; case 's': silent = 1; break; #ifdef DEBUG case 'T': /* Trace. */ if ((gp->tracefp = fopen(openbsd_optarg, "w")) == NULL) { openbsd_warn("%s", openbsd_optarg); goto err; } (void)fprintf(gp->tracefp, "\n===\ntrace: open %s\n", openbsd_optarg); fflush(gp->tracefp); break; #endif /* ifdef DEBUG */ case 't': /* Tag. */ if (flagchk == 'r') { openbsd_warnx( "only one of -r and -t may be specified."); return (1); } if (flagchk == 't') { openbsd_warnx("only one tag file may be specified."); return (1); } flagchk = 't'; tag_f = openbsd_optarg; break; case 'v': /* Vi mode. */ LF_CLR(SC_EX); LF_SET(SC_VI); break; case 'w': wsizearg = openbsd_optarg; break; case '?': default: (void)gp->scr_usage(); return (1); } argc -= openbsd_optind; (void)argc; argv += openbsd_optind; (void)argv; if (secure) if (openbsd_pledge( "stdio rpath wpath cpath fattr flock getpw tty", NULL) == -1) { perror("pledge"); goto err; } /* * -s option is only meaningful to ex. * * If not reading from a terminal, it's like -s was specified. */ if (silent && !LF_ISSET(SC_EX)) { openbsd_warnx("-s option is only applicable to ex."); goto err; } if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED)) silent = 1; /* * Build and initialize the first/current screen. This is a bit * tricky. If an error is returned, we may or may not have a * screen structure. If we have a screen structure, put it on a * display queue so that the error messages get displayed. * * !!! * Everything we do until we go interactive is done in ex mode. */ if (screen_init(gp, NULL, &sp)) { if (sp != NULL) TAILQ_INSERT_HEAD(&gp->dq, sp, q); goto err; } F_SET(sp, SC_EX); TAILQ_INSERT_HEAD(&gp->dq, sp, q); if (v_key_init(sp)) /* Special key initialization. */ goto err; { int oargs[5], *oargp = oargs; if (readonly) /* Command-line options. */ *oargp++ = O_READONLY; if (secure) *oargp++ = O_SECURE; *oargp = -1; /* Options initialization. */ if (opts_init(sp, oargs)) goto err; } if (wsizearg != NULL) { ARGS *av[2], a, b; (void)snprintf(path, sizeof(path), "window=%s", wsizearg); a.bp = (CHAR_T *)path; a.len = strlen(path); b.bp = NULL; b.len = 0; av[0] = &a; av[1] = &b; (void)opts_set(sp, av, NULL); } if (silent) { /* Ex batch mode option values. */ O_CLR(sp, O_AUTOPRINT); O_CLR(sp, O_PROMPT); O_CLR(sp, O_VERBOSE); O_CLR(sp, O_WARN); F_SET(sp, SC_EX_SILENT); } sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */ sp->cols = O_VAL(sp, O_COLUMNS); if (!silent && startup) { /* Read EXINIT, exrc files. */ if (ex_exrc(sp)) goto err; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } /* * List recovery files if -r specified without file arguments. * Note, options must be initialized and startup information * read before doing this. */ if (flagchk == 'r' && argv[0] == NULL) { if (rcv_list(sp)) goto err; if (screen_end(sp)) goto err; goto done; } /* * !!! * Initialize the default ^D, ^U scrolling value here, after the * user has had every opportunity to set the window option. * * It's historic practice that changing the value of the window * option did not alter the default scrolling value, only giving * a count to ^D/^U did that. */ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2; /* * If we don't have a command-line option, switch into the right * editor now, so that we position default files correctly, and * so that any tags file file-already-locked messages are in the * vi screen, not the ex screen. * * XXX * If we have a command-line option, the error message can end * up in the wrong place, but I think that the combination is * unlikely. */ if (gp->c_option == NULL) { F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI)); } /* Open a tag file if specified. */ if (tag_f != NULL && ex_tag_first(sp, tag_f)) goto err; /* * Append any remaining arguments as file names. Files are recovery * files if -r specified. If the tag option or ex startup commands * loaded a file, then any file arguments are going to come after it. */ if (*argv != NULL) { if (sp->frp != NULL) { size_t l; /* Cheat -- we know we have an extra argv slot. */ l = strlen(sp->frp->name) + 1; if ((*--argv = malloc(l)) == NULL) { openbsd_warn(NULL); goto err; } (void)openbsd_strlcpy(*argv, sp->frp->name, l); } sp->argv = sp->cargv = argv; F_SET(sp, SC_ARGNOFREE); if (flagchk == 'r') F_SET(sp, SC_ARGRECOVER); } /* * If the ex startup commands and or/the tag option haven't already * created a file, create one. If no command-line files were given, * use a temporary file. */ if (sp->frp == NULL) { if (sp->argv == NULL) { if ((frp = file_add(sp, NULL)) == NULL) goto err; } else { if ((frp = file_add(sp, (CHAR_T *)sp->argv[0])) == NULL) goto err; if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); } if (file_init(sp, frp, NULL, 0)) goto err; if (EXCMD_RUNNING(gp)) { (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (screen_end(sp)) goto err; goto done; } } } /* * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex * was forced to initialize the screen during startup. We'd like to * wait for a single character from the user, but we can't because * we're not in raw mode. We can't switch to raw mode because the * vi initialization will switch to xterm's alternate screen, causing * us to lose the messages we're pausing to make sure the user read. * So, wait for a complete line. */ if (F_ISSET(sp, SC_SCR_EX)) { p = msg_cmsg(sp, CMSG_CONT_R, &len); (void)!write(STDOUT_FILENO, p, len); for (;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; if (ev.e_event == E_INTERRUPT || (ev.e_event == E_CHARACTER && (ev.e_value == K_CR || ev.e_value == K_NL))) break; (void)gp->scr_bell(sp); } } /* Switch into the right editor, regardless. */ F_CLR(sp, SC_EX | SC_VI); F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT); /* * Main edit loop. Vi handles split screens itself, we only return * here when switching editor modes or restarting the screen. */ while (sp != NULL) if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp)) goto err; done: rval = 0; if (0) err: rval = 1; /* Clean out the global structure. */ v_end(gp); return (rval); } /* * v_end -- * End the program, discarding screens and most of the global area. * * PUBLIC: void v_end(GS *); */ void v_end(GS *gp) { MSGS *mp; SCR *sp; /* If there are any remaining screens, kill them off. */ if (gp->ccl_sp != NULL) { (void)file_end(gp->ccl_sp, NULL, 1); (void)screen_end(gp->ccl_sp); } while ((sp = TAILQ_FIRST(&gp->dq))) (void)screen_end(sp); /* Removes sp from the queue. */ while ((sp = TAILQ_FIRST(&gp->hq))) (void)screen_end(sp); /* Removes sp from the queue. */ #if defined(DEBUG) || defined(PURIFY) { FREF *frp; /* Free FREF's. */ while ((frp = TAILQ_FIRST(&gp->frefq))) { TAILQ_REMOVE(&gp->frefq, frp, q); free(frp->name); free(frp->tname); free(frp); } } /* Free key input queue. */ free(gp->i_event); /* Free cut buffers. */ cut_close(gp); /* Free map sequences. */ seq_close(gp); /* Free default buffer storage. */ (void)text_lfree(&gp->dcb_store.textq); #endif /* if defined(DEBUG) || defined(PURIFY) */ /* Ring the bell if scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) (void)fprintf(stderr, "\07"); /* \a */ /* * Flush any remaining messages. If a message is here, it's almost * certainly the message about the event that killed us (although * it's possible that the user is sourcing a file that exits from the * editor). */ while ((mp = LIST_FIRST(&gp->msgq)) != NULL) { (void)fprintf(stderr, "%s%.*s", mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf); LIST_REMOVE(mp, q); #if defined(DEBUG) || defined(PURIFY) free(mp->buf); free(mp); #endif /* if defined(DEBUG) || defined(PURIFY) */ } #if defined(DEBUG) || defined(PURIFY) /* Free any temporary space. */ free(gp->tmp_bp); # if defined(DEBUG) /* Close debugging file descriptor. */ if (gp->tracefp != NULL) (void)fclose(gp->tracefp); # endif /* if defined(DEBUG) */ #endif /* if defined(DEBUG) || defined(PURIFY) */ } /* * v_obsolete -- * Convert historic arguments into something * openbsd_getopt(3) will like. */ static int v_obsolete(char *argv[]) { size_t len; char *p; /* * Translate old style arguments into something openbsd_getopt * will like. Make sure it's not text space memory, because * ex modifies the strings. * Change "+" into "-c$". * Change "+" into "-c". * Change "-" into "-s" * The c, T, t and w options take arguments so they can't be * special arguments. * * Stop if we find "--" as an argument, the user may want to edit * a file named "+foo". */ while (*++argv && strcmp(argv[0], "--")) if (argv[0][0] == '+') { if (argv[0][1] == '\0') { argv[0] = strdup("-c$"); if (argv[0] == NULL) goto nomem; } else { p = argv[0]; len = strlen(argv[0]); if ((argv[0] = malloc(len + 2)) == NULL) goto nomem; argv[0][0] = '-'; argv[0][1] = 'c'; (void)openbsd_strlcpy(argv[0] + 2, p + 1, len); } } else if (argv[0][0] == '-') { if (argv[0][1] == '\0') { argv[0] = strdup("-s"); if (argv[0] == NULL) { nomem: openbsd_warn(NULL); return (1); } } else if ((argv[0][1] == 'c' || argv[0][1] == 'T' || argv[0][1] == 't' || argv[0][1] == 'w') && argv[0][2] == '\0') ++argv; } return (0); } #ifdef DEBUG static void attach(GS *gp) { int fd; char ch; if ((fd = open(_PATH_TTY, O_RDONLY)) < 0) { openbsd_warn("%s", _PATH_TTY); return; } (void)printf("process %ld waiting, enter to continue: ", (long)getpid()); (void)fflush(stdout); do { if (read(fd, &ch, 1) != 1) { (void)close(fd); return; } } while (ch != '\n' && ch != '\r'); (void)close(fd); } #endif /* ifdef DEBUG */ ================================================ FILE: common/mark.c ================================================ /* $OpenBSD: mark.c,v 1.14 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "common.h" static LMARK *mark_find(SCR *, CHAR_T); /* * Marks are maintained in a key sorted doubly linked list. We can't * use arrays because we have no idea how big an index key could be. * The underlying assumption is that users don't have more than, say, * 10 marks at any one time, so this will be is fast enough. * * Marks are fixed, and modifications to the line don't update the mark's * position in the line. This can be hard. If you add text to the line, * place a mark in that text, undo the addition and use ` to move to the * mark, the location will have disappeared. It's tempting to try to adjust * the mark with the changes in the line, but this is hard to do, especially * if we've given the line to v_ntext.c:v_ntext() for editing. Historic vi * would move to the first non-blank on the line when the mark location was * past the end of the line. This can be complicated by deleting to a mark * that has disappeared using the ` command. Historic vi treated this as * a line-mode motion and deleted the line. This implementation complains to * the user. * * In historic vi, marks returned if the operation was undone, unless the * mark had been subsequently reset. Tricky. This is hard to start with, * but in the presence of repeated undo it gets nasty. When a line is * deleted, we delete (and log) any marks on that line. An undo will create * the mark. Any mark creations are noted as to whether the user created * it or if it was created by an undo. The former cannot be reset by another * undo, but the latter may. * * All of these routines translate ABSMARK2 to ABSMARK1. Setting either of * the absolute mark locations sets both, so that "m'" and "m`" work like * they, ah, for lack of a better word, "should". */ /* * mark_init -- * Set up the marks. * * PUBLIC: int mark_init(SCR *, EXF *); */ int mark_init(SCR *sp, EXF *ep) { /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * * Set up the marks. */ LIST_INIT(&ep->marks); return (0); } /* * mark_end -- * Free up the marks. * * PUBLIC: int mark_end(SCR *, EXF *); */ int mark_end(SCR *sp, EXF *ep) { LMARK *lmp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ while ((lmp = LIST_FIRST(&ep->marks)) != NULL) { LIST_REMOVE(lmp, q); free(lmp); } return (0); } /* * mark_get -- * Get the location referenced by a mark. * * PUBLIC: int mark_get(SCR *, CHAR_T, MARK *, mtype_t); */ int mark_get(SCR *sp, CHAR_T key, MARK *mp, mtype_t mtype) { LMARK *lmp; if (key == ABSMARK2) key = ABSMARK1; lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { msgq(sp, mtype, "Mark %s: not set", KEY_NAME(sp, key)); return (1); } if (F_ISSET(lmp, MARK_DELETED)) { msgq(sp, mtype, "Mark %s: the line was deleted", KEY_NAME(sp, key)); return (1); } /* * !!! * The absolute mark is initialized to lno 1/cno 0, and historically * you could use it in an empty file. Make such a mark always work. */ if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) { msgq(sp, mtype, "Mark %s: cursor position no longer exists", KEY_NAME(sp, key)); return (1); } mp->lno = lmp->lno; mp->cno = lmp->cno; return (0); } /* * mark_set -- * Set the location referenced by a mark. * * PUBLIC: int mark_set(SCR *, CHAR_T, MARK *, int); */ int mark_set(SCR *sp, CHAR_T key, MARK *value, int userset) { LMARK *lmp, *lmt; if (key == ABSMARK2) key = ABSMARK1; /* * The rules are simple. If the user is setting a mark (if it's a * new mark this is always true), it always happens. If not, it's * an undo, and we set it if it's not already set or if it was set * by a previous undo. */ lmp = mark_find(sp, key); if (lmp == NULL || lmp->name != key) { MALLOC_RET(sp, lmt, sizeof(LMARK)); if (lmp == NULL) { LIST_INSERT_HEAD(&sp->ep->marks, lmt, q); } else LIST_INSERT_AFTER(lmp, lmt, q); lmp = lmt; } else if (!userset && !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET)) return (0); lmp->lno = value->lno; lmp->cno = value->cno; lmp->name = key; lmp->flags = userset ? MARK_USERSET : 0; return (0); } /* * mark_find -- * Find the requested mark, or, the slot immediately before * where it would go. */ static LMARK * mark_find(SCR *sp, CHAR_T key) { LMARK *lmp, *lastlmp; /* * Return the requested mark or the slot immediately before * where it should go. */ for (lastlmp = NULL, lmp = LIST_FIRST(&sp->ep->marks); lmp != NULL; lastlmp = lmp, lmp = LIST_NEXT(lmp, q)) if (lmp->name >= key) return (lmp->name == key ? lmp : lastlmp); return (lastlmp); } /* * mark_insdel -- * Update the marks based on an insertion or deletion. * * PUBLIC: int mark_insdel(SCR *, lnop_t, recno_t); */ int mark_insdel(SCR *sp, lnop_t op, recno_t lno) { LMARK *lmp; recno_t lline; switch (op) { case LINE_APPEND: /* All insert/append operations are done as inserts. */ abort(); case LINE_DELETE: LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->lno >= lno) { if (lmp->lno == lno) { F_SET(lmp, MARK_DELETED); (void)log_mark(sp, lmp); } else --lmp->lno; } break; case LINE_INSERT: /* * XXX * Very nasty special case. If the file was empty, then we're * adding the first line, which is a replacement. So, we don't * modify the marks. This is a hack to make: * * mz:r!echo foo'z * * work, i.e. historically you could mark the "line" in an empty * file and replace it, and continue to use the mark. Insane, * well, yes, I know, but someone complained. * * Check for line #2 before going to the end of the file. */ if (!db_exist(sp, 2)) { if (db_last(sp, &lline)) return (1); if (lline == 1) return (0); } LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->lno >= lno) ++lmp->lno; break; case LINE_RESET: break; } return (0); } ================================================ FILE: common/mark.h ================================================ /* $OpenBSD: mark.h,v 1.5 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)mark.h 10.3 (Berkeley) 3/6/96 */ /* * The MARK and LMARK structures define positions in the file. There are * two structures because the mark subroutines are the only places where * anything cares about something other than line and column. * * Because of the different interfaces used by the db(3) package, curses, * and users, the line number is 1 based and the column number is 0 based. * Additionally, it is known that the out-of-band line number is less than * any legal line number. The line number is of type recno_t, as that's * the underlying type of the database. The column number is of type size_t, * guaranteeing that we can malloc a line. */ struct _mark { #define OOBLNO 0 /* Out-of-band line number. */ recno_t lno; /* Line number. */ size_t cno; /* Column number. */ }; struct _lmark { LIST_ENTRY(_lmark) q; /* Linked list of marks. */ recno_t lno; /* Line number. */ size_t cno; /* Column number. */ CHAR_T name; /* Mark name. */ #define MARK_DELETED 0x01 /* Mark was deleted. */ #define MARK_USERSET 0x02 /* User set this mark. */ u_int8_t flags; }; #define ABSMARK1 '\'' /* Absolute mark name. */ #define ABSMARK2 '`' /* Absolute mark name. */ ================================================ FILE: common/mem.h ================================================ /* $OpenBSD: mem.h,v 1.10 2017/06/24 16:30:47 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)mem.h 10.7 (Berkeley) 3/30/96 */ /* * Increase the size of a malloc'd buffer. Two versions, one that * returns, one that jumps to an error label. */ #define BINC_GOTO(sp, lp, llen, nlen) { \ void *L__bincp; \ if ((nlen) > (llen)) { \ if ((L__bincp = binc((sp), (lp), &(llen), (nlen))) \ == NULL) \ goto alloc_err; \ /* \ * !!! \ * Possible pointer conversion. \ */ \ (lp) = L__bincp; \ } \ } #define BINC_RET(sp, lp, llen, nlen) { \ void *L__bincp; \ if ((nlen) > (llen)) { \ if ((L__bincp = binc((sp), (lp), &(llen), (nlen))) \ == NULL) \ return (1); \ /* \ * !!! \ * Possible pointer conversion. \ */ \ (lp) = L__bincp; \ } \ } /* * Get some temporary space, preferably from the global temporary buffer, * from a malloc'd buffer otherwise. Two versions, one that returns, one * that jumps to an error label. */ #define GET_SPACE_GOTO(sp, bp, blen, nlen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ (bp) = NULL; \ (blen) = 0; \ BINC_GOTO((sp), (bp), (blen), (nlen)); \ } else { \ BINC_GOTO((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen));\ (bp) = L__gp->tmp_bp; \ (blen) = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ } #define GET_SPACE_RET(sp, bp, blen, nlen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \ (bp) = NULL; \ (blen) = 0; \ BINC_RET((sp), (bp), (blen), (nlen)); \ } else { \ BINC_RET((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen)); \ (bp) = L__gp->tmp_bp; \ (blen) = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } \ } /* * Add space to a GET_SPACE returned buffer. Two versions, one that * returns, one that jumps to an error label. */ #define ADD_SPACE_GOTO(sp, bp, blen, nlen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp != NULL && (bp) == L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_GOTO((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen));\ (bp) = L__gp->tmp_bp; \ (blen) = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_GOTO((sp), (bp), (blen), (nlen)); \ } #define ADD_SPACE_RET(sp, bp, blen, nlen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp != NULL && (bp) == L__gp->tmp_bp) { \ F_CLR(L__gp, G_TMP_INUSE); \ BINC_RET((sp), L__gp->tmp_bp, L__gp->tmp_blen, (nlen)); \ (bp) = L__gp->tmp_bp; \ (blen) = L__gp->tmp_blen; \ F_SET(L__gp, G_TMP_INUSE); \ } else \ BINC_RET((sp), (bp), (blen), (nlen)); \ } /* Free a GET_SPACE returned buffer. */ #define FREE_SPACE(sp, bp, blen) { \ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \ if (L__gp != NULL && (bp) == L__gp->tmp_bp) \ F_CLR(L__gp, G_TMP_INUSE); \ else \ free(bp); \ } /* * Malloc a buffer, casting the return pointer. Various versions. */ #define CALLOC(sp, p, nmemb, size) { \ if (((p) = calloc((nmemb), (size))) == NULL) \ msgq((sp), M_SYSERR, NULL); \ } #define CALLOC_GOTO(sp, p, nmemb, size) { \ if (((p) = calloc((nmemb), (size))) == NULL) \ goto alloc_err; \ } #define CALLOC_RET(sp, p, nmemb, size) { \ if (((p) = calloc((nmemb), (size))) == NULL) { \ msgq((sp), M_SYSERR, NULL); \ return (1); \ } \ } #define MALLOC(sp, p, size) { \ if (((p) = malloc(size)) == NULL) \ msgq((sp), M_SYSERR, NULL); \ } #define MALLOC_GOTO(sp, p, size) { \ if (((p) = malloc(size)) == NULL) \ goto alloc_err; \ } #define MALLOC_RET(sp, p, size) { \ if (((p) = malloc(size)) == NULL) { \ msgq((sp), M_SYSERR, NULL); \ return (1); \ } \ } #define REALLOC(sp, p, size) { \ void *tmpp; \ if (((tmpp) = (realloc((p), (size)))) == NULL) { \ msgq((sp), M_SYSERR, NULL); \ free(p); \ } \ p = tmpp; \ } #define REALLOCARRAY(sp, p, nelem, size) { \ void *tmpp; \ if (((tmpp) = \ (openbsd_reallocarray((p), (nelem), (size)))) == NULL) { \ msgq((sp), M_SYSERR, NULL); \ free(p); \ } \ p = tmpp; \ } /* * Versions of memmove(3) and memset(3) that use the size of the * initial pointer to figure out how much memory to manipulate. */ #define MEMMOVE(p, t, len) memmove((p), (t), (len) * sizeof(*(p))) #define MEMSET(p, value, len) memset( (p), (value), (len) * sizeof(*(p))) ================================================ FILE: common/msg.c ================================================ /* $OpenBSD: msg.c,v 1.28 2022/12/26 19:16:03 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" /* * msgq -- * Display a message. * * PUBLIC: void msgq(SCR *, mtype_t, const char *, ...); */ void msgq(SCR *sp, mtype_t mt, const char *fmt, ...) { static int reenter; /* STATIC: Re-entrancy check. */ GS *gp; size_t blen, len, mlen, nlen; const char *p; char *bp, *mp; va_list ap; /* * !!! * It's possible to enter msg when there's no screen to hold the * message. If sp is NULL, ignore the special cases and put the * message out to stderr. */ if (sp == NULL) { gp = NULL; if (mt == M_BERR) mt = M_ERR; else if (mt == M_VINFO) mt = M_INFO; } else { gp = sp->gp; switch (mt) { case M_BERR: if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) { F_SET(gp, G_BELLSCHED); return; } mt = M_ERR; break; case M_XINFO: mt = M_INFO; break; case M_VINFO: if (!O_ISSET(sp, O_VERBOSE)) return; mt = M_INFO; /* FALLTHROUGH */ case M_INFO: if (F_ISSET(sp, SC_EX_SILENT)) return; break; case M_ERR: case M_SYSERR: break; default: abort(); } } /* * It's possible to reenter msg when it allocates space. We're * probably dead anyway, but there's no reason to drop core. * * XXX * Yes, there's a race, but it should only be two instructions. */ if (reenter++) return; /* Get space for the message. */ nlen = 1024; if (0) { retry: FREE_SPACE(sp, bp, blen); nlen *= 2; } bp = NULL; blen = 0; GET_SPACE_GOTO(sp, bp, blen, nlen); /* * Error prefix. * * mp: pointer to the current next character to be written * mlen: length of the already written characters * blen: total length of the buffer */ #define REM (blen - mlen) mp = bp; mlen = 0; if (mp == NULL) return; if (mt == M_SYSERR) { p = "Error: "; len = strlen(p); if (REM < len) goto retry; memcpy(mp, p, len); mp += len; mlen += len; } /* * If we're running an ex command that the user didn't enter, display * the file name and line number prefix. */ if ((mt == M_ERR || mt == M_SYSERR) && sp != NULL && gp != NULL && gp->if_name != NULL) { for (p = gp->if_name; *p != '\0'; ++p) { len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p)); mp += len; if ((mlen += len) > blen) goto retry; } len = snprintf(mp, REM, ", %d: ", gp->if_lno); mp += len; if ((mlen += len) > blen) goto retry; } /* If nothing to format, we're done. */ if (fmt == NULL) { len = 0; goto nofmt; } /* Format the arguments into the string. */ va_start(ap, fmt); len = vsnprintf(mp, REM, fmt, ap); va_end(ap); if (len >= nlen) goto retry; nofmt: mp += len; if ((mlen += len) > blen) goto retry; if (mt == M_SYSERR) { len = snprintf(mp, REM, ": %s", strerror(errno)); mp += len; if ((mlen += len) > blen) goto retry; mt = M_ERR; } /* Add trailing newline. */ if ((mlen += 1) > blen) goto retry; *mp = '\n'; if (sp != NULL) (void)ex_fflush(sp); if (gp != NULL) gp->scr_msg(sp, mt, bp, mlen); else (void)fprintf(stderr, "%.*s", (int)mlen, bp); /* Cleanup. */ FREE_SPACE(sp, bp, blen); alloc_err: reenter = 0; } /* * msgq_str -- * Display a message with an embedded string. * * PUBLIC: void msgq_str(SCR *, mtype_t, char *, char *); */ void msgq_str(SCR *sp, mtype_t mtype, char *str, char *fmt) { int nf, sv_errno; char *p; if (str == NULL) { msgq(sp, mtype, fmt); return; } sv_errno = errno; p = msg_print(sp, str, &nf); errno = sv_errno; msgq(sp, mtype, fmt, p); if (nf) FREE_SPACE(sp, p, 0); } /* * mod_rpt -- * Report on the lines that changed. * * !!! * Historic vi documentation (USD:15-8) claimed that "The editor will also * always tell you when a change you make affects text which you cannot see." * This wasn't true -- edit a large file and do "100d|1". We don't implement * this semantic since it requires tracking each line that changes during a * command instead of just keeping count. * * Line counts weren't right in historic vi, either. For example, given the * file: * abc * def * the command 2d}, from the 'b' would report that two lines were deleted, * not one. * * PUBLIC: void mod_rpt(SCR *); */ void mod_rpt(SCR *sp) { static char * const action[] = { "added", "changed", "deleted", "joined", "moved", "shifted", "yanked", }; static char * const lines[] = { "line", "lines", }; recno_t total; unsigned long rptval; int first, cnt; size_t blen, len, tlen; const char *t; char * const *ap; char *bp, *p; /* Change reports are turned off in batch mode. */ if (F_ISSET(sp, SC_EX_SILENT)) return; /* Reset changing line number. */ sp->rptlchange = OOBLNO; /* * Don't build a message if not enough changed. * * !!! * And now, a vi clone test. Historically, vi reported if the number * of changed lines was > than the value, not >=, unless it was a yank * command, which used >=. No lie. Furthermore, an action was never * reported for a single line action. This is consistent for actions * other than yank, but yank didn't report single line actions even if * the report edit option was set to 1. In addition, setting report to * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an * unknown reason (this bug was fixed in System III/V at some point). * I got complaints, so nvi conforms to System III/V historic practice * except that we report a yank of 1 line if report is set to 1. */ #define ARSIZE(a) sizeof(a) / sizeof (*a) #define MAXNUM 25 rptval = O_VAL(sp, O_REPORT); for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt) total += sp->rptlines[cnt]; if (total == 0) return; if (total <= rptval && sp->rptlines[L_YANKED] < rptval) { for (cnt = 0; cnt < ARSIZE(action); ++cnt) sp->rptlines[cnt] = 0; return; } /* Build and display the message. */ GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1); for (p = bp, first = 1, tlen = 0, ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt) if (sp->rptlines[cnt] != 0) { if (first) first = 0; else { *p++ = ';'; *p++ = ' '; tlen += 2; } len = snprintf(p, MAXNUM, "%u ", sp->rptlines[cnt]); p += len; tlen += len; t = lines[sp->rptlines[cnt] == 1 ? 0 : 1]; len = strlen(t); memcpy(p, t, len); p += len; tlen += len; *p++ = ' '; ++tlen; len = strlen(*ap); memcpy(p, *ap, len); p += len; tlen += len; sp->rptlines[cnt] = 0; } /* Add trailing newline. */ *p = '\n'; ++tlen; (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, bp, tlen); FREE_SPACE(sp, bp, blen); alloc_err: return; #undef ARSIZE #undef MAXNUM } /* * msgq_status -- * Report on the file's status. * * PUBLIC: void msgq_status(SCR *, recno_t, unsigned int); */ void msgq_status(SCR *sp, recno_t lno, unsigned int flags) { recno_t last; size_t blen, len; int cnt, needsep; const char *t; char **ap, *bp, *np, *p, *s, *ep; /* Get sufficient memory. */ len = strlen(sp->frp->name); GET_SPACE_GOTO(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128); p = bp; ep = bp + blen; if (p == NULL) return; /* Copy in the filename. */ for (t = sp->frp->name; *t != '\0'; ++t) { len = KEY_LEN(sp, *t); memcpy(p, KEY_NAME(sp, *t), len); p += len; } np = p; *p++ = ':'; *p++ = ' '; /* Copy in the argument count. */ if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) { for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt); if (cnt > 1) { (void)snprintf(p, ep - p, "%'d files to edit", cnt); p += strlen(p); *p++ = ':'; *p++ = ' '; } F_CLR(sp, SC_STATUS_CNT); } /* * See nvi/exf.c:file_init() for a description of how and when the * read-only bit is set. * * !!! * The historic display for "name changed" was "[Not edited]". */ needsep = 0; if (F_ISSET(sp->frp, FR_NEWFILE)) { F_CLR(sp->frp, FR_NEWFILE); len = strlen("new file"); memcpy(p, "new file", len); p += len; needsep = 1; } else { if (F_ISSET(sp->frp, FR_NAMECHANGE)) { len = strlen("name changed"); memcpy(p, "name changed", len); p += len; needsep = 1; } if (needsep) { *p++ = ','; *p++ = ' '; } t = (F_ISSET(sp->ep, F_MODIFIED)) ? "modified" : "unmodified"; len = strlen(t); memcpy(p, t, len); p += len; needsep = 1; } #ifndef _AIX if (F_ISSET(sp->frp, FR_UNLOCKED)) { if (needsep) { *p++ = ','; *p++ = ' '; } len = strlen("UNLOCKED"); memcpy(p, "UNLOCKED", len); p += len; needsep = 1; } #endif /* ifndef _AIX */ if (O_ISSET(sp, O_READONLY)) { if (needsep) { *p++ = ','; *p++ = ' '; } len = strlen("readonly"); memcpy(p, "readonly", len); p += len; needsep = 1; } if (needsep) { *p++ = ':'; *p++ = ' '; } if (LF_ISSET(MSTAT_SHOWLAST)) { if (db_last(sp, &last)) last = 0; if (last == 0) { char* mtfilestr = "empty file"; len = strlen(mtfilestr); memcpy(p, mtfilestr, len); p += len; } else { (void)snprintf(p, ep - p, "line %'lu of %'lu [%lu%%]", (unsigned long)lno, (unsigned long)last, (unsigned long)(lno * 100) / last); p += strlen(p); } } else { if (db_last(sp, &last)) last = 0; (void)snprintf(p, ep - p, "line %'lu", (unsigned long)lno); p += strlen(p); if (last) { (void)snprintf(p, ep - p, " of %'lu", (unsigned long)last); p += strlen(p); } } #ifdef DEBUG (void)snprintf(p, ep - p, " (pid %ld)", (long)getpid()); p += strlen(p); #endif /* ifdef DEBUG */ *p++ = '\n'; len = p - bp; /* * There's a nasty problem with long path names. Tags files * can result in long paths and vi will request a continuation key from * the user as soon as it starts the screen. Unfortunately, the user * has already typed ahead, and chaos results. If we assume that the * characters in the filenames and informational messages only take a * single screen column each, we can trim the filename. * * XXX * Status lines get put up at fairly awkward times. For example, when * you do a filter read (e.g., :read ! echo foo) in the top screen of a * split screen, we have to repaint the status lines for all the screens * below the top screen. We don't want users having to enter continue * characters for those screens. Make it really hard to screw this up. */ s = bp; if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) { for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s); if (s == np) { s = p - (sp->cols - 5); *--s = ' '; } *--s = '.'; *--s = '.'; *--s = '.'; len = p - s; } /* Flush any waiting ex messages. */ (void)ex_fflush(sp); sp->gp->scr_msg(sp, M_INFO, s, len); FREE_SPACE(sp, bp, blen); alloc_err: return; } /* * msg_cont -- * Return common continuation messages. * * PUBLIC: const char *msg_cmsg(SCR *, cmsg_t, size_t *); */ const char * msg_cmsg(SCR *sp, cmsg_t which, size_t *lenp) { const char *s; switch (which) { case CMSG_CONF: s = "Confirm? [y/n/q]"; break; case CMSG_CONT: s = "Press any key to continue: "; break; case CMSG_CONT_EX: s = "Press any key to continue [: to enter more ex commands]: "; break; case CMSG_CONT_R: s = "Press Enter to continue: "; break; case CMSG_CONT_S: s = " Cont?"; break; case CMSG_CONT_Q: s = "Press any key to continue [q to quit]: "; break; default: abort(); } *lenp = strlen(s); return s; } /* * msg_print -- * Return a printable version of a string, in allocated memory. * * PUBLIC: char *msg_print(SCR *, const char *, int *); */ char * msg_print(SCR *sp, const char *s, int *needfree) { size_t blen, nlen; const char *cp; char *bp, *ep, *p, *t; *needfree = 0; for (cp = s; *cp != '\0'; ++cp) if (!isprint(*cp)) break; if (*cp == '\0') return ((char *)s); /* SAFE: needfree set to 0. */ nlen = 0; if (0) { retry: if (sp == NULL) free(bp); else FREE_SPACE(sp, bp, blen); *needfree = 0; } nlen += 256; if (sp == NULL) { if ((bp = malloc(nlen)) == NULL) goto alloc_err; blen = 0; } else GET_SPACE_GOTO(sp, bp, blen, nlen); if (0) { alloc_err: return (""); } *needfree = 1; for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp) for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++); if (p == ep) goto retry; *p = '\0'; return (bp); } ================================================ FILE: common/msg.h ================================================ /* $OpenBSD: msg.h,v 1.3 2001/01/29 01:58:31 niklas Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)msg.h 10.10 (Berkeley) 5/10/96 */ /* * Common messages (continuation or confirmation). */ typedef enum { CMSG_CONF, CMSG_CONT, CMSG_CONT_EX, CMSG_CONT_R, CMSG_CONT_S, CMSG_CONT_Q } cmsg_t; /* * Message types. * * !!! * In historical vi, O_VERBOSE didn't exist, and O_TERSE made the error * messages shorter. In this implementation, O_TERSE has no effect and * O_VERBOSE results in informational displays about common errors, for * naive users. * * M_NONE Display to the user, no reformatting, no nothing. * * M_BERR Error: M_ERR if O_VERBOSE, else bell. * M_ERR Error: Display in inverse video. * M_INFO Info: Display in normal video, except in silent mode. * M_SYSERR Error: M_ERR, using strerror(3) message. * M_VINFO Info: M_INFO if O_VERBOSE, else ignore. * M_XINFO Info: Like M_INFO, but ignoring silent mode setting. * * The underlying message display routines only need to know about M_NONE, * M_ERR and M_INFO -- all the other message types are converted into one * of them by the message routines. */ typedef enum { M_NONE = 1, M_BERR, M_ERR, M_INFO, M_SYSERR, M_VINFO, M_XINFO } mtype_t; /* * There are major problems with error messages being generated by routines * preparing the screen to display error messages. It's possible for the * editor to generate messages before we have a screen in which to display * them, or during the transition between ex (and vi startup) and a true vi. * There's a queue in the global area to hold them. * * If SC_EX/SC_VI is set, that's the mode that the editor is in. If the flag * S_SCREEN_READY is set, that means that the screen is prepared to display * messages. */ typedef struct _msgh MSGH; /* MSGS list head structure. */ LIST_HEAD(_msgh, _msg); struct _msg { LIST_ENTRY(_msg) q; /* Linked list of messages. */ mtype_t mtype; /* Message type: M_NONE, M_ERR, M_INFO. */ char *buf; /* Message buffer. */ size_t len; /* Message length. */ }; /* Flags to msgq_status(). */ #define MSTAT_SHOWLAST 0x01 /* Show the line number of the last line. */ #define MSTAT_TRUNCATE 0x02 /* Truncate the file name if it's too long. */ ================================================ FILE: common/options.awk ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 # The Regents of the University of California # Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 # Keith Bostic # Copyright (c) 2021-2024 Jeffrey H. Johnson BEGIN { printf("enum {\n"); first = 1; } /^\/\* O_[0-9A-Z_]*/ { printf("\t%s%s,\n", $2, first ? " = 0" : ""); first = 0; next; } END { printf("\tO_OPTIONCOUNT\n};\n"); } ================================================ FILE: common/options.c ================================================ /* $OpenBSD: options.c,v 1.30 2024/02/12 16:42:42 job Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "errc.h" #include "common.h" #include "../vi/vi.h" #include "pathnames.h" #undef open static int opts_abbcmp(const void *, const void *); static int opts_cmp(const void *, const void *); static int opts_print(SCR *, OPTLIST const *); int f_imctrl (SCR *, OPTION *, char *, unsigned long *); /* * O'Reilly noted options and abbreviations are from "Learning the VI Editor", * Fifth Edition, May 1992. There's no way of knowing what systems they are * actually from. * * HPUX noted options and abbreviations are from "The Ultimate Guide to the * VI and EX Text Editors", 1990. */ OPTLIST const optlist[] = { /* O_ALTNOTATION Nvi2 */ {"altnotation", f_print, OPT_0BOOL, OPT_EARLYSET}, /* O_ALTWERASE 4.4BSD */ {"altwerase", f_altwerase, OPT_0BOOL, 0}, /* O_AUTOINDENT 4BSD */ {"autoindent", NULL, OPT_0BOOL, 0}, /* O_AUTOPRINT 4BSD */ {"autoprint", NULL, OPT_1BOOL, 0}, /* O_AUTOWRITE 4BSD */ {"autowrite", NULL, OPT_0BOOL, 0}, /* O_BACKUP 4.4BSD */ {"backup", NULL, OPT_STR, 0}, /* O_BEAUTIFY 4BSD */ {"beautify", NULL, OPT_0BOOL, 0}, /* O_BSERASE OpenVi */ {"bserase", NULL, OPT_0BOOL, 0}, /* O_CDPATH 4.4BSD */ {"cdpath", NULL, OPT_STR, 0}, /* O_CEDIT 4.4BSD */ {"cedit", NULL, OPT_STR, 0}, /* O_COLUMNS 4.4BSD */ {"columns", f_columns, OPT_NUM, OPT_NOSAVE}, /* O_COMMENT 4.4BSD */ {"comment", NULL, OPT_0BOOL, 0}, /* O_EDCOMPATIBLE 4BSD */ {"edcompatible",NULL, OPT_0BOOL, 0}, /* O_ESCAPETIME 4.4BSD */ {"escapetime", NULL, OPT_NUM, 0}, /* O_ERRORBELLS 4BSD */ {"errorbells", NULL, OPT_0BOOL, 0}, /* O_EXPANDTAB NetBSD 5.0 */ {"expandtab", NULL, OPT_0BOOL, 0}, /* O_EXRC System V (undocumented) */ {"exrc", NULL, OPT_0BOOL, 0}, /* O_EXTENDED 4.4BSD */ {"extended", f_recompile, OPT_0BOOL, 0}, /* O_FILEC 4.4BSD */ {"filec", NULL, OPT_STR, 0}, /* O_FLASH HPUX */ {"flash", NULL, OPT_0BOOL, 0}, /* O_HARDTABS 4BSD */ {"hardtabs", NULL, OPT_NUM, 0}, /* O_ICLOWER 4.4BSD */ {"iclower", f_recompile, OPT_0BOOL, 0}, /* O_IGNORECASE 4BSD */ {"ignorecase", f_recompile, OPT_0BOOL, 0}, /* O_IMCTRL nvi-m17n-nb */ {"imctrl", f_imctrl, OPT_0BOOL, 0}, /* O_IMKEY nvi-m17n-nb */ {"imkey", NULL, OPT_STR, 0}, /* O_KEYTIME 4.4BSD */ {"keytime", NULL, OPT_NUM, 0}, /* O_LEFTRIGHT 4.4BSD */ {"leftright", f_reformat, OPT_0BOOL, 0}, /* O_LINES 4.4BSD */ {"lines", f_lines, OPT_NUM, OPT_NOSAVE}, /* O_LIST 4BSD */ {"list", f_reformat, OPT_0BOOL, 0}, /* O_LOCKFILES 4.4BSD */ {"lock", NULL, OPT_1BOOL, 0}, /* O_MAGIC 4BSD */ {"magic", NULL, OPT_1BOOL, 0}, /* O_MATCHTIME 4.4BSD */ {"matchtime", NULL, OPT_NUM, 0}, /* O_MESG 4BSD */ {"mesg", NULL, OPT_1BOOL, 0}, /* O_NOPRINT 4.4BSD */ {"noprint", f_print, OPT_STR, OPT_EARLYSET}, /* O_NUMBER 4BSD */ {"number", f_reformat, OPT_0BOOL, 0}, /* O_OCTAL 4.4BSD */ {"octal", f_print, OPT_0BOOL, OPT_EARLYSET}, /* O_OPEN 4BSD */ {"open", NULL, OPT_1BOOL, 0}, /* O_PARAGRAPHS 4BSD */ {"paragraphs", f_paragraph, OPT_STR, 0}, /* O_PATH 4.4BSD */ {"path", NULL, OPT_STR, 0}, /* O_PRINT 4.4BSD */ {"print", f_print, OPT_STR, OPT_EARLYSET}, /* O_PROMPT 4BSD */ {"prompt", NULL, OPT_1BOOL, 0}, /* O_READONLY 4BSD (undocumented) */ {"readonly", f_readonly, OPT_0BOOL, OPT_ALWAYS}, /* O_RECDIR 4.4BSD */ {"recdir", NULL, OPT_STR, 0}, /* O_REMAP 4BSD */ {"remap", NULL, OPT_1BOOL, 0}, /* O_REPORT 4BSD */ {"report", NULL, OPT_NUM, 0}, /* O_RULER 4.4BSD */ {"ruler", NULL, OPT_0BOOL, 0}, /* O_SCROLL 4BSD */ {"scroll", NULL, OPT_NUM, 0}, /* O_SEARCHINCR 4.4BSD */ {"searchincr", NULL, OPT_0BOOL, 0}, /* O_SECTIONS 4BSD */ {"sections", f_section, OPT_STR, 0}, /* O_SECURE 4.4BSD */ {"secure", f_secure, OPT_0BOOL, OPT_NOUNSET}, /* O_SHELL 4BSD */ {"shell", NULL, OPT_STR, 0}, /* O_SHELLMETA 4.4BSD */ {"shellmeta", NULL, OPT_STR, 0}, /* O_SHIFTWIDTH 4BSD */ {"shiftwidth", NULL, OPT_NUM, OPT_NOZERO}, /* O_SHOWMATCH 4BSD */ {"showmatch", NULL, OPT_0BOOL, 0}, /* O_SHOWFILENAME */ {"showfilename",NULL, OPT_0BOOL, 0}, /* O_SHOWMODE 4.4BSD */ {"showmode", NULL, OPT_0BOOL, 0}, /* O_SIDESCROLL 4.4BSD */ {"sidescroll", NULL, OPT_NUM, OPT_NOZERO}, /* O_TABSTOP 4BSD */ {"tabstop", f_reformat, OPT_NUM, OPT_NOZERO}, /* O_TAGLENGTH 4BSD */ {"taglength", NULL, OPT_NUM, 0}, /* O_TAGS 4BSD */ {"tags", NULL, OPT_STR, 0}, /* O_TERM 4BSD * !!! * By default, the historic vi always displayed information about two * options, redraw and term. Term seems sufficient. */ {"term", NULL, OPT_STR, OPT_ADISP|OPT_NOSAVE}, /* O_TERSE 4BSD */ {"terse", NULL, OPT_0BOOL, 0}, /* O_TILDEOP 4.4BSD */ {"tildeop", NULL, OPT_0BOOL, 0}, /* O_TIMEOUT 4BSD (undocumented) */ {"timeout", NULL, OPT_1BOOL, 0}, /* O_TTYWERASE 4.4BSD */ {"ttywerase", f_ttywerase, OPT_0BOOL, 0}, /* O_VERBOSE 4.4BSD */ {"verbose", NULL, OPT_0BOOL, 0}, /* O_VISIBLETAB OpenVi */ {"visibletab", f_reformat, OPT_0BOOL, 0}, /* O_W1200 4BSD */ {"w1200", f_w1200, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W300 4BSD */ {"w300", f_w300, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W9600 4BSD */ {"w9600", f_w9600, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_WARN 4BSD */ {"warn", NULL, OPT_1BOOL, 0}, /* O_WINDOW 4BSD */ {"window", f_window, OPT_NUM, OPT_NOZERO}, /* O_WINDOWNAME 4BSD */ {"windowname", NULL, OPT_0BOOL, 0}, /* O_WRAPLEN 4.4BSD */ {"wraplen", NULL, OPT_NUM, 0}, /* O_WRAPMARGIN 4BSD */ {"wrapmargin", NULL, OPT_NUM, 0}, /* O_WRAPSCAN 4BSD */ {"wrapscan", NULL, OPT_1BOOL, 0}, /* O_WRITEANY 4BSD */ {"writeany", NULL, OPT_0BOOL, 0}, {NULL, NULL, 255, 0}, }; typedef struct abbrev { char *name; int offset; } OABBREV; static OABBREV const abbrev[] = { {"ai", O_AUTOINDENT}, /* 4BSD */ {"an", O_ALTNOTATION}, /* Nvi2 */ {"ap", O_AUTOPRINT}, /* 4BSD */ {"aw", O_AUTOWRITE}, /* 4BSD */ {"bf", O_BEAUTIFY}, /* 4BSD */ {"bse", O_BSERASE}, /* OpenVi */ {"co", O_COLUMNS}, /* 4.4BSD */ {"eb", O_ERRORBELLS}, /* 4BSD */ {"ed", O_EDCOMPATIBLE}, /* 4BSD */ {"et", O_EXPANDTAB}, /* NetBSD 5.0 */ {"ex", O_EXRC}, /* System V (undocumented) */ {"ht", O_HARDTABS}, /* 4BSD */ {"ic", O_IGNORECASE}, /* 4BSD */ {"li", O_LINES}, /* 4.4BSD */ {"nu", O_NUMBER}, /* 4BSD */ {"para", O_PARAGRAPHS}, /* 4BSD */ {"ro", O_READONLY}, /* 4BSD (undocumented) */ {"scr", O_SCROLL}, /* 4BSD (undocumented) */ {"sect", O_SECTIONS}, /* O'Reilly */ {"sh", O_SHELL}, /* 4BSD */ {"sm", O_SHOWMATCH}, /* 4BSD */ {"smd", O_SHOWMODE}, /* 4BSD */ {"sw", O_SHIFTWIDTH}, /* 4BSD */ {"tag", O_TAGS}, /* 4BSD (undocumented) */ {"tl", O_TAGLENGTH}, /* 4BSD */ {"to", O_TIMEOUT}, /* 4BSD (undocumented) */ {"ts", O_TABSTOP}, /* 4BSD */ {"tty", O_TERM}, /* 4BSD (undocumented) */ {"ttytype", O_TERM}, /* 4BSD (undocumented) */ {"vt", O_VISIBLETAB}, /* OpenVi */ {"w", O_WINDOW}, /* O'Reilly */ {"wa", O_WRITEANY}, /* 4BSD */ {"wi", O_WINDOW}, /* 4BSD (undocumented) */ {"wl", O_WRAPLEN}, /* 4.4BSD */ {"wm", O_WRAPMARGIN}, /* 4BSD */ {"ws", O_WRAPSCAN}, /* 4BSD */ {NULL, 255}, }; /* * opts_init -- * Initialize some of the options. * * PUBLIC: int opts_init(SCR *, int *); */ int opts_init(SCR *sp, int *oargs) { ARGS *argv[2], a, b; OPTLIST const *op; unsigned long v; int optindx; char *s, b1[1024]; a.bp = b1; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; /* Set numeric and string default values. */ #define OI_b1(indx) { \ a.len = strlen(b1); \ if (opts_set(sp, argv, NULL)) { \ optindx = indx; \ goto err; \ } \ } #define OI(indx, str) { \ (void)openbsd_strlcpy(b1, (str), sizeof(b1)); \ OI_b1(indx); \ } /* * Indirect global options to global space. Specifically, set up * terminal, lines, columns first, they're used by other options. * Note, don't set the flags until we've set up the indirection. */ if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) { optindx = O_TERM; goto err; } F_SET(&sp->opts[O_TERM], OPT_GLOBAL); if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) { optindx = O_LINES; goto err; } F_SET(&sp->opts[O_LINES], OPT_GLOBAL); if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) { optindx = O_COLUMNS; goto err; } F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL); if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) { optindx = O_SECURE; goto err; } F_SET(&sp->opts[O_SECURE], OPT_GLOBAL); /* Initialize string values. */ (void)snprintf(b1, sizeof(b1), "cdpath=%s", (s = getenv("CDPATH")) == NULL ? ":" : s); OI_b1(O_CDPATH); OI(O_ESCAPETIME, "escapetime=2"); OI(O_FILEC, "filec=\t"); OI(O_KEYTIME, "keytime=6"); OI(O_MATCHTIME, "matchtime=7"); OI(O_REPORT, "report=5"); OI(O_PARAGRAPHS, "paragraphs=IPLPPPQPP LIpplpipbpBlBdPpLpIt"); OI(O_PATH, "path="); (void)snprintf(b1, sizeof(b1), "recdir=%s", _PATH_PRESERVE); OI_b1(O_RECDIR); OI(O_SECTIONS, "sections=NHSHH HUnhshShSs"); (void)snprintf(b1, sizeof(b1), "shell=%s", (s = getenv("SHELL")) == NULL ? _PATH_BSHELL : s); OI_b1(O_SHELL); OI(O_SHELLMETA, "shellmeta=~{[*?$`'\"\\"); OI(O_SHIFTWIDTH, "shiftwidth=8"); OI(O_SIDESCROLL, "sidescroll=16"); OI(O_TABSTOP, "tabstop=8"); (void)snprintf(b1, sizeof(b1), "tags=%s", _PATH_TAGS); OI_b1(O_TAGS); OI(O_IMKEY, "imkey=/?aioAIO"); /* * XXX * Initialize O_SCROLL here, after term; initializing term should * have created a LINES/COLUMNS value. */ if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0) v = 1; (void)snprintf(b1, sizeof(b1), "scroll=%ld", v); OI_b1(O_SCROLL); /* * The default window option values are: * 8 if baud rate <= 600 * 16 if baud rate <= 1200 * LINES - 1 if baud rate > 1200 * * Note, the windows option code will correct any too-large value * or when the O_LINES value is 1. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 600) v = 8; else if (v <= 1200) v = 16; else v = O_VAL(sp, O_LINES) - 1; (void)snprintf(b1, sizeof(b1), "window=%lu", v); OI_b1(O_WINDOW); /* * Set boolean default values, and copy all settings into the default * information. OS_NOFREE is set, we're copying, not replacing. */ for (op = optlist, optindx = 0; op->name != NULL; ++op, ++optindx) switch (op->type) { case OPT_0BOOL: break; case OPT_1BOOL: O_SET(sp, optindx); O_D_SET(sp, optindx); break; case OPT_NUM: o_set(sp, optindx, OS_DEF, NULL, O_VAL(sp, optindx)); break; case OPT_STR: if (O_STR(sp, optindx) != NULL && o_set(sp, optindx, OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, optindx), 0)) goto err; break; default: abort(); } /* * !!! * Some options can be initialized by the command name or the * command-line arguments. They don't set the default values, * it's historic practice. */ for (; *oargs != -1; ++oargs) OI(*oargs, optlist[*oargs].name); return (0); #undef OI #undef OI_b1 err: msgq(sp, M_ERR, "Unable to set default %s option", optlist[optindx].name); return (1); } /* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set(SCR *, ARGS *[], char *); */ int opts_set(SCR *sp, ARGS *argv[], char *usage) { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; unsigned long value, turnoff; int ch, equals, nf, nf2, offset, qmark, rval; char *endp, *name, *p, *sep, *t; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!strcmp(argv[0]->bp, "all")) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == 'n' && name[1] == 'o') { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_str(sp, M_ERR, name, "set: the %s option may not be turned off"); rval = 1; break; } if (equals) { msgq_str(sp, M_ERR, name, "set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS)) { if (turnoff) { if (!O_ISSET(sp, offset)) break; } else { if (O_ISSET(sp, offset)) break; } } if (F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (turnoff) O_CLR(sp, offset); else O_SET(sp, offset); } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, NULL, &turnoff)) || ex_optchange(sp, offset, NULL, &turnoff) || v_optchange(sp, offset, NULL, &turnoff) || sp->gp->scr_optchange(sp, offset, NULL, &turnoff)) { rval = 1; break; } if (!F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (turnoff) O_CLR(sp, offset); else O_SET(sp, offset); } break; case OPT_NUM: if (turnoff) { msgq_str(sp, M_ERR, name, "set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!isdigit(sep[0])) goto badnum; if ((nret = nget_uslong(&value, sep, &endp, 10)) != NUM_OK) { p = msg_print(sp, name, &nf); t = msg_print(sp, sep, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "set: %s option: %s", p, t); break; case NUM_OVER: msgq(sp, M_ERR, "set: %s option: %s: value overflow", p, t); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); rval = 1; break; } if (*endp && !isblank(*endp)) { badnum: p = msg_print(sp, name, &nf); t = msg_print(sp, sep, &nf2); msgq(sp, M_ERR, "set: %s option: %s is an illegal number", p, t); if (nf) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_str(sp, M_ERR, name, "set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; if (F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) { rval = 1; break; } } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, sep, &value)) || ex_optchange(sp, offset, sep, &value) || v_optchange(sp, offset, sep, &value) || sp->gp->scr_optchange(sp, offset, sep, &value)) { rval = 1; break; } if (!F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; } break; case OPT_STR: if (turnoff) { msgq_str(sp, M_ERR, name, "set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), sep)) break; if (F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, sep, 0)) { rval = 1; break; } } /* Report to subsystems. */ if ((op->func != NULL && op->func(sp, spo, sep, NULL)) || ex_optchange(sp, offset, sep, NULL) || v_optchange(sp, offset, sep, NULL) || sp->gp->scr_optchange(sp, offset, sep, NULL)) { rval = 1; break; } if (!F_ISSET(op, OPT_EARLYSET)) { /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, sep, 0)) rval = 1; } break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); } /* * o_set -- * Set an option's value. * * PUBLIC: int o_set(SCR *, int, unsigned int, char *, unsigned long); */ int o_set(SCR *sp, int opt, unsigned int flags, char *str, unsigned long val) { OPTION *op; /* Set a pointer to the options area. */ op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ? &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt]; /* Copy the string, if requested. */ if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Free the previous string, if requested, and set the value. */ if (LF_ISSET(OS_DEF)) if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE) && op->o_def.str != NULL) free(op->o_def.str); op->o_def.str = str; } else op->o_def.val = val; else if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE) && op->o_cur.str != NULL) free(op->o_cur.str); op->o_cur.str = str; } else op->o_cur.val = val; return (0); } /* * opts_empty -- * Return 1 if the string option is invalid, 0 if it's OK. * * PUBLIC: int opts_empty(SCR *, int, int); */ int opts_empty(SCR *sp, int off, int silent) { char *p; if ((p = O_STR(sp, off)) == NULL || p[0] == '\0') { if (!silent) msgq_str(sp, M_ERR, optlist[off].name, "No %s edit option specified"); return (1); } return (0); } /* * opts_dump -- * List the current values of selected options. * * PUBLIC: void opts_dump(SCR *, enum optdisp); */ void opts_dump(SCR *sp, enum optdisp type) { OPTLIST const *op; int base, b_num, cnt, col, colwidth, curlen, s_num; int numcols, numrows, row; int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT]; char nbuf[20]; /* * Options are output in two groups -- those that fit in a column and * those that don't. Output is done on 6 character "tab" boundaries * for no particular reason. (Since we don't output tab characters, * we can ignore the terminal's tab settings.) Ignore the user's tab * setting because we have no idea how reasonable it is. * * Find a column width we can live with, testing from 10 columns to 1. */ for (numcols = 10; numcols > 1; --numcols) { colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1); if (colwidth >= 10) { colwidth = (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1); numcols = sp->cols / colwidth; break; } colwidth = 0; } /* * Get the set of options to list, entering them into * the column list or the overflow list. */ for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) { cnt = op - optlist; /* If OPT_NDISP set, it's never displayed. */ if (F_ISSET(op, OPT_NDISP)) continue; switch (type) { case ALL_DISPLAY: /* Display all. */ break; case CHANGED_DISPLAY: /* Display changed. */ /* If OPT_ADISP set, it's always "changed". */ if (F_ISSET(op, OPT_ADISP)) break; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: case OPT_NUM: if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt)) continue; break; case OPT_STR: if (O_STR(sp, cnt) == O_D_STR(sp, cnt) || (O_D_STR(sp, cnt) != NULL && !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt)))) continue; break; } break; case SELECT_DISPLAY: /* Display selected. */ if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED)) continue; break; default: case NO_DISPLAY: abort(); } F_CLR(&sp->opts[cnt], OPT_SELECTED); curlen = strlen(op->name); switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (!O_ISSET(sp, cnt)) curlen += 2; break; case OPT_NUM: (void)snprintf(nbuf, sizeof(nbuf), "%ld", O_VAL(sp, cnt)); curlen += strlen(nbuf); break; case OPT_STR: if (O_STR(sp, cnt) != NULL) curlen += strlen(O_STR(sp, cnt)); curlen += 3; break; } /* Offset by 2 so there's a gap. */ if (curlen <= colwidth - 2) s_op[s_num++] = cnt; else b_op[b_num++] = cnt; } if (s_num > 0) { /* Figure out the number of rows. */ if ((s_num > 0) && (numcols > 0) && (s_num > numcols)) { numrows = s_num / numcols; if (s_num % numcols) ++numrows; } else numrows = 1; /* Display the options in sorted order. */ for (row = 0; row < numrows;) { for (base = row, col = 0; col < numcols; ++col) { cnt = opts_print(sp, &optlist[s_op[base]]); if ((base += numrows) >= s_num) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); } if (++row < numrows || b_num) (void)ex_puts(sp, "\n"); } } for (row = 0; row < b_num;) { (void)opts_print(sp, &optlist[b_op[row]]); if (++row < b_num) (void)ex_puts(sp, "\n"); } (void)ex_puts(sp, "\n"); } /* * opts_print -- * Print out an option. */ static int opts_print(SCR *sp, OPTLIST const *op) { int curlen, offset; curlen = 0; offset = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: curlen += ex_printf(sp, "%s%s", O_ISSET(sp, offset) ? "" : "no", op->name); break; case OPT_NUM: curlen += ex_printf(sp, "%s=%ld", op->name, O_VAL(sp, offset)); break; case OPT_STR: curlen += ex_printf(sp, "%s=\"%s\"", op->name, O_STR(sp, offset) == NULL ? "" : O_STR(sp, offset)); break; } return (curlen); } /* * opts_save -- * Write the current configuration to a file. * * PUBLIC: int opts_save(SCR *, FILE *); */ int opts_save(SCR *sp, FILE *fp) { OPTLIST const *op; int ch, cnt; char *p; for (op = optlist; op->name != NULL; ++op) { if (F_ISSET(op, OPT_NOSAVE)) continue; cnt = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (O_ISSET(sp, cnt)) (void)fprintf(fp, "set %s\n", op->name); else (void)fprintf(fp, "set no%s\n", op->name); break; case OPT_NUM: (void)fprintf(fp, "set %s=%-3ld\n", op->name, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) == NULL) break; (void)fprintf(fp, "set "); for (p = op->name; (ch = *p) != '\0'; ++p) { if (isblank(ch) || ch == '\\') (void)putc('\\', fp); (void)putc(ch, fp); } (void)putc('=', fp); for (p = O_STR(sp, cnt); (ch = *p) != '\0'; ++p) { if (isblank(ch) || ch == '\\') (void)putc('\\', fp); (void)putc(ch, fp); } (void)putc('\n', fp); break; } if (ferror(fp)) { msgq(sp, M_SYSERR, NULL); return (1); } } return (0); } /* * opts_search -- * Search for an option. * * PUBLIC: OPTLIST const *opts_search(char *); */ OPTLIST const * opts_search(char *name) { OPTLIST const *op, *found; OABBREV atmp, *ap; OPTLIST otmp; size_t len; /* Check list of abbreviations. */ atmp.name = name; if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1, sizeof(OABBREV), opts_abbcmp)) != NULL) return (optlist + ap->offset); /* Check list of options. */ otmp.name = name; if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1, sizeof(OPTLIST), opts_cmp)) != NULL) return (op); /* * Check to see if the name is the prefix of one (and only one) * option. If so, return the option. */ len = strlen(name); for (found = NULL, op = optlist; op->name != NULL; ++op) { if (op->name[0] < name[0]) continue; if (op->name[0] > name[0]) break; if (!memcmp(op->name, name, len)) { if (found != NULL) return (NULL); found = op; } } return (found); } /* * opts_nomatch -- * Standard nomatch error message for options. * * PUBLIC: void opts_nomatch(SCR *, char *); */ void opts_nomatch(SCR *sp, char *name) { msgq_str(sp, M_ERR, name, "set: no %s option: 'set all' gives all option values"); } static int opts_abbcmp(const void *a, const void *b) { return(strcmp(((OABBREV *)a)->name, ((OABBREV *)b)->name)); } static int opts_cmp(const void *a, const void *b) { return(strcmp(((OPTLIST *)a)->name, ((OPTLIST *)b)->name)); } /* * opts_copy -- * Copy a screen's OPTION array. * * PUBLIC: int opts_copy(SCR *, SCR *); */ int opts_copy(SCR *orig, SCR *sp) { int cnt, rval; /* Copy most everything without change. */ memcpy(sp->opts, orig->opts, sizeof(orig->opts)); /* Copy the string edit options. */ for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&optlist[cnt], OPT_GLOBAL)) continue; /* * If never set, or already failed, NULL out the entries -- * have to continue after failure, otherwise would have two * screens referencing the same memory. */ if (rval || O_STR(sp, cnt) == NULL) { o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0); o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); continue; } /* Copy the current string. */ if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) { o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); goto nomem; } /* Copy the default string. */ if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) { nomem: msgq(orig, M_SYSERR, NULL); rval = 1; } } return (rval); } /* * opts_free -- * Free all option strings * * PUBLIC: void opts_free(SCR *); */ void opts_free(SCR *sp) { int cnt; for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&optlist[cnt], OPT_GLOBAL)) continue; free(O_STR(sp, cnt)); free(O_D_STR(sp, cnt)); } } ================================================ FILE: common/options.h ================================================ /* $OpenBSD: options.h,v 1.9 2017/07/03 07:01:14 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)options.h 10.19 (Berkeley) 10/10/96 */ /* * Edit option information. Historically, if you set a boolean or numeric * edit option value to its "default" value, it didn't show up in the :set * display, i.e. it wasn't considered "changed". String edit options would * show up as changed, regardless. We maintain a parallel set of values * which are the default values and never consider an edit option changed * if it was reset to the default value. * * Macros to retrieve boolean, integral and string option values, and to * set, clear and test boolean option values. Some options (secure, lines, * columns, terminal type) are global in scope, and are therefore stored * in the global area. The offset in the global options array is stored * in the screen's value field. This is set up when the options are first * initialized. */ #define O_V(sp, o, fld) \ (F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ? \ (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld : \ (sp)->opts[(o)].fld) /* Global option macros. */ #define OG_CLR(gp, o) ((gp)->opts[(o)].o_cur.val) = 0 #define OG_SET(gp, o) ((gp)->opts[(o)].o_cur.val) = 1 #define OG_STR(gp, o) ((gp)->opts[(o)].o_cur.str) #define OG_VAL(gp, o) ((gp)->opts[(o)].o_cur.val) #define OG_ISSET(gp, o) OG_VAL((gp), (o)) #define OG_D_STR(gp, o) ((gp)->opts[(o)].o_def.str) #define OG_D_VAL(gp, o) ((gp)->opts[(o)].o_def.val) /* * Flags to o_set(); need explicit OS_STR as can be setting the value to * NULL. */ #define OS_DEF 0x01 /* Set the default value. */ #define OS_NOFREE 0x02 /* Don't free the old string. */ #define OS_STR 0x04 /* Set to string argument. */ #define OS_STRDUP 0x08 /* Copy then set to string argument. */ struct _option { union { unsigned long val; /* Value or boolean. */ char *str; /* String. */ } o_cur; #define O_CLR(sp, o) o_set((sp), (o), 0, NULL, 0) #define O_SET(sp, o) o_set((sp), (o), 0, NULL, 1) #define O_STR(sp, o) O_V((sp), (o), o_cur.str) #define O_VAL(sp, o) O_V((sp), (o), o_cur.val) #define O_ISSET(sp, o) O_VAL((sp), (o)) union { unsigned long val; /* Value or boolean. */ char *str; /* String. */ } o_def; #define O_D_CLR(sp, o) o_set((sp), (o), OS_DEF, NULL, 0) #define O_D_SET(sp, o) o_set((sp), (o), OS_DEF, NULL, 1) #define O_D_STR(sp, o) O_V((sp), (o), o_def.str) #define O_D_VAL(sp, o) O_V((sp), (o), o_def.val) #define O_D_ISSET(sp, o) O_D_VAL((sp), (o)) #define OPT_GLOBAL 0x01 /* Option is global. */ #define OPT_SELECTED 0x02 /* Selected for display. */ u_int8_t flags; }; /* List of option names, associated update functions and information. */ struct _optlist { char *name; /* Name. */ /* Change function. */ int (*func)(SCR *, OPTION *, char *, unsigned long *); /* Type of object. */ enum { OPT_0BOOL, OPT_1BOOL, OPT_NUM, OPT_STR } type; #define OPT_ADISP 0x001 /* Always display the option. */ #define OPT_ALWAYS 0x002 /* Always call the support function. */ #define OPT_NDISP 0x004 /* Never display the option. */ #define OPT_NOSAVE 0x008 /* Mkexrc command doesn't save. */ #define OPT_NOUNSET 0x020 /* Option may not be unset. */ #define OPT_NOZERO 0x040 /* Option may not be set to 0. */ #define OPT_EARLYSET 0x080 /* Func called after value is set */ u_int8_t flags; }; /* Option argument to opts_dump(). */ enum optdisp { NO_DISPLAY, ALL_DISPLAY, CHANGED_DISPLAY, SELECT_DISPLAY }; /* Options array. */ extern OPTLIST const optlist[]; #ifdef O_PATH # undef O_PATH /* bits/fcntl-linux.h may have defined O_PATH. */ #endif /* ifdef O_PATH */ #include "options_def.h" ================================================ FILE: common/options_f.c ================================================ /* $OpenBSD: options_f.c,v 1.13 2019/05/21 09:24:58 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #undef open /* * PUBLIC: int f_altwerase(SCR *, OPTION *, char *, unsigned long *); */ int f_altwerase(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (!*valp) O_CLR(sp, O_TTYWERASE); return (0); } /* * PUBLIC: int f_columns(SCR *, OPTION *, char *, unsigned long *); */ int f_columns(SCR *sp, OPTION *op, char *str, unsigned long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "Screen columns too small, less than %d", MINIMUM_SCREEN_COLS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_COLS 3640 if (*valp > MAXIMUM_SCREEN_COLS) { msgq(sp, M_ERR, "Screen columns too large, greater than %d", MAXIMUM_SCREEN_COLS); return (1); } return (0); } /* * PUBLIC: int f_lines(SCR *, OPTION *, char *, unsigned long *); */ int f_lines(SCR *sp, OPTION *op, char *str, unsigned long *valp) { /* Validate the number. */ if (*valp < MINIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "Screen lines too small, less than %d", MINIMUM_SCREEN_ROWS); return (1); } /* * !!! * It's not uncommon for allocation of huge chunks of memory to cause * core dumps on various systems. So, we prune out numbers that are * "obviously" wrong. Vi will not work correctly if it has the wrong * number of lines/columns for the screen, but at least we don't drop * core. */ #define MAXIMUM_SCREEN_ROWS 2048 if (*valp > MAXIMUM_SCREEN_ROWS) { msgq(sp, M_ERR, "Screen lines too large, greater than %d", MAXIMUM_SCREEN_ROWS); return (1); } /* * Set the value, and the related scroll value. If no window * value set, set a new default window. */ o_set(sp, O_LINES, 0, NULL, *valp); if (*valp == 1) { sp->defscroll = 1; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, 1); o_set(sp, O_WINDOW, OS_DEF, NULL, 1); } } else { sp->defscroll = (*valp - 1) / 2; if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) || O_VAL(sp, O_WINDOW) > *valp) { o_set(sp, O_WINDOW, 0, NULL, *valp - 1); o_set(sp, O_WINDOW, OS_DEF, NULL, *valp - 1); } } return (0); } /* * PUBLIC: int f_paragraph(SCR *, OPTION *, char *, unsigned long *); */ int f_paragraph(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (strlen(str) & 1) { msgq(sp, M_ERR, "The paragraph option must be in two character groups"); return (1); } return (0); } /* * PUBLIC: int f_print(SCR *, OPTION *, char *, unsigned long *); */ int f_print(SCR *sp, OPTION *op, char *str, unsigned long *valp) { /* Reinitialize the key fast lookup table. */ v_key_ilookup(sp); /* Reformat the screen. */ F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_readonly(SCR *, OPTION *, char *, unsigned long *); */ int f_readonly(SCR *sp, OPTION *op, char *str, unsigned long *valp) { /* * !!! * See the comment in exf.c. */ if (*valp) F_CLR(sp, SC_READONLY); else F_SET(sp, SC_READONLY); return (0); } /* * PUBLIC: int f_recompile(SCR *, OPTION *, char *, unsigned long *); */ int f_recompile(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } return (0); } /* * PUBLIC: int f_reformat(SCR *, OPTION *, char *, unsigned long *); */ int f_reformat(SCR *sp, OPTION *op, char *str, unsigned long *valp) { F_SET(sp, SC_SCR_REFORMAT); return (0); } /* * PUBLIC: int f_section(SCR *, OPTION *, char *, unsigned long *); */ int f_section(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (strlen(str) & 1) { msgq(sp, M_ERR, "The section option must be in two character groups"); return (1); } return (0); } /* * PUBLIC: int f_secure(SCR *, OPTION *, char *, unsigned long *) */ int f_secure(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (openbsd_pledge( "stdio rpath wpath cpath fattr flock getpw tty", NULL) == -1) { msgq(sp, M_ERR, "pledge failed"); return (1); } return (0); } /* * PUBLIC: int f_ttywerase(SCR *, OPTION *, char *, unsigned long *); */ int f_ttywerase(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (!*valp) O_CLR(sp, O_ALTWERASE); return (0); } /* * PUBLIC: int f_w300(SCR *, OPTION *, char *, unsigned long *); */ int f_w300(SCR *sp, OPTION *op, char *str, unsigned long *valp) { unsigned long v; /* Historical behavior for w300 was < 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v >= 1200) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w1200(SCR *, OPTION *, char *, unsigned long *); */ int f_w1200(SCR *sp, OPTION *op, char *str, unsigned long *valp) { unsigned long v; /* Historical behavior for w1200 was == 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v < 1200 || v > 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_w9600(SCR *, OPTION *, char *, unsigned long *); */ int f_w9600(SCR *sp, OPTION *op, char *str, unsigned long *valp) { unsigned long v; /* Historical behavior for w9600 was > 1200. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 4800) return (0); return (f_window(sp, op, str, valp)); } /* * PUBLIC: int f_window(SCR *, OPTION *, char *, unsigned long *); */ int f_window(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (*valp >= O_VAL(sp, O_LINES) - 1 && (*valp = O_VAL(sp, O_LINES) - 1) == 0) *valp = 1; return (0); } /* * PUBLIC: int f_imctrl __P((SCR *, OPTION *, char *, unsigned long *)); */ int f_imctrl(SCR *sp, OPTION *op, char *str, unsigned long *valp) { if (*valp) sp->gp->scr_imctrl(sp, IMCTRL_INIT); return (0); } ================================================ FILE: common/put.c ================================================ /* $OpenBSD: put.c,v 1.17 2025/08/23 21:02:10 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "common.h" /* * put -- * Put text buffer contents into the file. * * PUBLIC: int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int, int) */ int put(SCR *sp, CB *cbp, CHAR_T *namep, MARK *cp, MARK *rp, int append, int cnt) { CHAR_T name; TEXT *ltp, *tp; recno_t lno; size_t blen, clen, len; int rval, i, isempty;; char *bp, *p, *t; if (cbp == NULL) { if (namep == NULL) { cbp = sp->gp->dcbp; if (cbp == NULL) { msgq(sp, M_ERR, "The default buffer is empty"); return (1); } } else { name = *namep; CBNAME(sp, cbp, name); if (cbp == NULL) { msgq(sp, M_ERR, "Buffer %s is empty", KEY_NAME(sp, name)); return (1); } } } tp = TAILQ_FIRST(&cbp->textq); /* * It's possible to do a put into an empty file, meaning that the cut * buffer simply becomes the file. It's a special case so that we can * ignore it in general. * * !!! * Historically, pasting into a file with no lines in vi would preserve * the single blank line. This is surely a result of the fact that the * historic vi couldn't deal with a file that had no lines in it. This * implementation treats that as a bug, and does not retain the blank * line. * * Historical practice is that the cursor ends at the first character * in the file. */ if (cp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0 && F_ISSET(cbp, CB_LMODE)) { for (i = cnt; i > 0; i--) { for (; tp; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); tp = TAILQ_FIRST(&cbp->textq); } rp->lno = 1; rp->cno = 0; return (0); } } /* If a line mode buffer, append each new line into the file. */ if (F_ISSET(cbp, CB_LMODE)) { lno = append ? cp->lno : cp->lno - 1; rp->lno = lno + 1; for (i = cnt; i > 0; i--) { for (; tp; ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) return (1); tp = TAILQ_FIRST(&cbp->textq); } rp->cno = 0; (void)nonblank(sp, rp->lno, &rp->cno); return (0); } /* * If buffer was cut in character mode, replace the current line with * one built from the portion of the first line to the left of the * split plus the first line in the CB. Append each intermediate line * in the CB. Append a line built from the portion of the first line * to the right of the split plus the last line in the CB. * * Get the first line. */ lno = cp->lno; if (db_eget(sp, lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } GET_SPACE_RET(sp, bp, blen, tp->len + len + 1); t = bp; if (bp == NULL) return (1); /* Original line, left of the split. */ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) { memcpy(bp, p, clen); p += clen; t += clen; } if (t == NULL) return (1); /* First line from the CB. */ if (tp->len != 0) { for (i = cnt; i > 0; i--) { memcpy(t, tp->lb, tp->len); t += tp->len; } } /* Calculate length left in the original line. */ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0)); /* * !!! * In the historical 4BSD version of vi, character mode puts within * a single line have two cursor behaviors: if the put is from the * unnamed buffer, the cursor moves to the character inserted which * appears last in the file. If the put is from a named buffer, * the cursor moves to the character inserted which appears first * in the file. In System III/V, it was changed at some point and * the cursor always moves to the first character. In both versions * of vi, character mode puts that cross line boundaries leave the * cursor on the first character. Nvi implements the System III/V * behavior, and expect POSIX.2 to do so as well. */ rp->lno = lno; rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0); /* * If no more lines in the CB, append the rest of the original * line and quit. Otherwise, build the last line before doing * the intermediate lines, because the line changes will lose * the cached line. */ if (TAILQ_NEXT(tp, q) == NULL) { if (clen > 0) { memcpy(t, p, clen); t += clen; } if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } } else { /* * Have to build both the first and last lines of the * put before doing any sets or we'll lose the cached * line. Build both the first and last lines in the * same buffer, so we don't have to have another buffer * floating around. * * Last part of original line; check for space, reset * the pointer into the buffer. */ ltp = TAILQ_LAST(&cbp->textq, _texth); len = t - bp; ADD_SPACE_RET(sp, bp, blen, ltp->len + clen); t = bp + len; /* Add in last part of the CB. */ memcpy(t, ltp->lb, ltp->len); if (clen) memcpy(t + ltp->len, p, clen); clen += ltp->len; /* * Now: bp points to the first character of the first * line, t points to the last character of the last * line, t - bp is the length of the first line, and * clen is the length of the last. Just figured you'd * want to know. * * Output the line replacing the original line. */ if (db_set(sp, lno, bp, t - bp)) goto err; if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* Output any intermediate lines in the CB. */ for (tp = TAILQ_NEXT(tp, q); TAILQ_NEXT(tp, q); ++lno, ++sp->rptlines[L_ADDED], tp = TAILQ_NEXT(tp, q)) if (db_append(sp, 1, lno, tp->lb, tp->len)) goto err; if (db_append(sp, 1, lno, t, clen)) goto err; ++sp->rptlines[L_ADDED]; } rval = 0; if (0) err: rval = 1; FREE_SPACE(sp, bp, blen); return (rval); } ================================================ FILE: common/recover.c ================================================ /* $OpenBSD: recover.c,v 1.32 2022/02/20 19:45:51 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include /* * We include , because the open #defines were found there * on historical systems. We also include because the open(2) * #defines are found there on newer systems. */ #include #include #include #include #ifdef __solaris__ # define _XPG7 #endif /* ifdef __solaris__ */ #include #include #include #include #include #include #include #include #include #include #include #include #include "errc.h" #include "common.h" #include "pathnames.h" #undef open /* * Recovery code. * * The basic scheme is as follows: In the EXF structure, we maintain full * paths of a b+tree file and a mail recovery file. The former is the file * used as backing store by the DB package. The latter is the file that * contains an email message to be sent to the user if we crash. The two * simple states of recovery are: * * + first starting the edit session: * the b+tree file exists and is mode 700, the mail recovery * file doesn't exist. * + after the file has been modified: * the b+tree file exists and is mode 600, the mail recovery * file exists, and is exclusively locked. * * In the EXF structure we maintain a file descriptor that is the locked * file descriptor for the mail recovery file. NOTE: we sometimes have to * do locking with fcntl(2). This is a problem because if you close(2) any * file descriptor associated with the file, ALL of the locks go away. Be * sure to remember that if you have to modify the recovery code. (It has * been rhetorically asked of what the designers could have been thinking * when they did that interface. The answer is simple: they weren't.) * * To find out if a recovery file/backing file pair are in use, try to get * a lock on the recovery file. * * To find out if a backing file can be deleted at boot time, check for an * owner execute bit. (Yes, I know it's ugly, but it's either that or put * special stuff into the backing file itself, or correlate the files at * boot time, neither of which looks like fun.) Note also that there's a * window between when the file is created and the X bit is set. It's small, * but it's there. To fix the window, check for 0 length files as well. * * To find out if a file can be recovered, check the F_RCV_ON bit. Note, * this DOES NOT mean that any initialization has been done, only that we * haven't yet failed at setting up or doing recovery. * * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit. * If that bit is not set when ending a file session: * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL, * they are unlink(2)'d, and free(3)'d. * If the EXF file descriptor (rcv_fd) is not -1, it is closed. * * The backing b+tree file is set up when a file is first edited, so that * the DB package can use it for on-disk caching and/or to snapshot the * file. When the file is first modified, the mail recovery file is created, * the backing file permissions are updated, the file is sync(2)'d to disk, * and the timer is started. Then, at RCV_PERIOD second intervals, the * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which * means that the data structures (SCR, EXF, the underlying tree structures) * must be consistent when the signal arrives. * * The recovery mail file contains normal mail headers, with two additions, * which occur in THIS order, as the FIRST TWO headers: * * X-vi-recover-file: file_name * X-vi-recover-path: recover_path * * Since newlines delimit the headers, this means that file names cannot have * newlines in them, but that's probably okay. As these files aren't intended * to be long-lived, changing their format won't be too painful. * * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX". */ #define VI_FHEADER "X-vi-recover-file: " #define VI_PHEADER "X-vi-recover-path: " int rcv_copy(SCR *, int, char *); void rcv_email(SCR *, int); int rcv_mailfile(SCR *, int, char *); char *rcv_gets(char *, size_t, int); int rcv_mktemp(SCR *, char *, char *, int); int rcv_openat(SCR *, int, const char *, int *); /* * rcv_tmp -- * Build a file name that will be used as the recovery file. * * PUBLIC: int rcv_tmp(SCR *, EXF *, char *); */ int rcv_tmp(SCR *sp, EXF *ep, char *name) { struct stat sb; static int warned = 0; int fd; char *dp, *p, path[PATH_MAX]; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. */ if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); if (stat(dp, &sb)) { if (errno == ENOENT) goto err_quiet; if (!warned) { warned = 1; msgq(sp, M_SYSERR, "%s", dp); goto err; } return 1; } /* Newlines delimit the mail messages. */ for (p = name; *p; ++p) if (*p == '\n') { msgq(sp, M_ERR, "Files with newlines in the name are unrecoverable"); goto err; } (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp); if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1) goto err; (void)close(fd); if ((ep->rcv_path = strdup(path)) == NULL) { msgq(sp, M_SYSERR, NULL); (void)unlink(path); err: msgq(sp, M_ERR, "Modifications not recoverable if the session fails"); err_quiet: return (1); } /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_init -- * Force the file to be snapshotted for recovery. * * PUBLIC: int rcv_init(SCR *); */ int rcv_init(SCR *sp) { EXF *ep; recno_t lno; ep = sp->ep; /* Only do this once. */ F_CLR(ep, F_FIRSTMODIFY); /* If we already know the file isn't recoverable, we're done. */ if (!F_ISSET(ep, F_RCV_ON)) return (0); /* Turn off recoverability until we figure out if this will work. */ F_CLR(ep, F_RCV_ON); /* Test if we're recovering a file, not editing one. */ if (ep->rcv_mpath == NULL) { /* Build a file to mail to the user. */ if (rcv_mailfile(sp, 0, NULL)) goto err; /* Force a read of the entire file. */ if (db_last(sp, &lno)) goto err; /* Turn on a busy message, and sync it to backing store. */ sp->gp->scr_busy(sp, "Copying file for recovery...", BUSY_ON); if (ep->db->sync(ep->db, R_RECNOSYNC)) { msgq_str(sp, M_SYSERR, ep->rcv_path, "Preservation failed: %s"); sp->gp->scr_busy(sp, NULL, BUSY_OFF); goto err; } sp->gp->scr_busy(sp, NULL, BUSY_OFF); } /* Turn off the owner execute bit. */ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); err: msgq(sp, M_ERR, "Modifications not recoverable if the session fails"); return (1); } /* * rcv_sync -- * Sync the file, optionally: * flagging the backup file to be preserved * snapshotting the backup file and send email to the user * sending email to the user if the file was modified * ending the file session * * PUBLIC: int rcv_sync(SCR *, unsigned int); */ int rcv_sync(SCR *sp, unsigned int flags) { EXF *ep; int fd, rval; char *dp, buf[1024]; /* Make sure that there's something to recover/sync. */ ep = sp->ep; if (ep == NULL || !F_ISSET(ep, F_RCV_ON)) return (0); /* Sync the file if it's been modified. */ if (F_ISSET(ep, F_MODIFIED)) { /* Clear recovery sync flag. */ F_CLR(ep, F_RCV_SYNC); if (ep->db->sync(ep->db, R_RECNOSYNC)) { F_CLR(ep, F_RCV_ON | F_RCV_NORM); msgq_str(sp, M_SYSERR, ep->rcv_path, "File backup failed: %s"); return (1); } /* REQUEST: don't remove backing file on exit. */ if (LF_ISSET(RCV_PRESERVE)) F_SET(ep, F_RCV_NORM); /* REQUEST: send email. */ if (LF_ISSET(RCV_EMAIL)) rcv_email(sp, ep->rcv_fd); } /* * !!! * Each time the user exec's :preserve, we have to snapshot all of * the recovery information, i.e. it's like the user re-edited the * file. We copy the DB(3) backing file, and then create a new mail * recovery file, it's simpler than exiting and reopening all of the * underlying files. * * REQUEST: snapshot the file. */ rval = 0; if (LF_ISSET(RCV_SNAPSHOT)) { if (opts_empty(sp, O_RECDIR, 0)) goto err; dp = O_STR(sp, O_RECDIR); (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp); if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1) goto err; sp->gp->scr_busy(sp, "Copying file for recovery...", BUSY_ON); if (rcv_copy(sp, fd, ep->rcv_path) || close(fd) || rcv_mailfile(sp, 1, buf)) { (void)unlink(buf); (void)close(fd); rval = 1; } sp->gp->scr_busy(sp, NULL, BUSY_OFF); } if (0) { err: rval = 1; } /* REQUEST: end the file session. */ if (LF_ISSET(RCV_ENDSESSION)) F_SET(sp, SC_EXIT_FORCE); return (rval); } /* * rcv_mailfile -- * Build the file to mail to the user. */ int rcv_mailfile(SCR *sp, int issync, char *cp_path) { EXF *ep; GS *gp; struct passwd *pw; size_t len; time_t now; uid_t uid; int fd; char *dp, *p, *t, buf[4096], mpath[PATH_MAX]; char *t1, *t2, *t3; char host[HOST_NAME_MAX+1]; gp = sp->gp; (void)gp; if ((pw = getpwuid(uid = getuid())) == NULL) { msgq(sp, M_ERR, "Information on user id %u not found", uid); return (1); } if (opts_empty(sp, O_RECDIR, 0)) return (1); dp = O_STR(sp, O_RECDIR); (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp); if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1) return (1); /* * XXX * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. There's an obvious window between the mkstemp call * and the lock, but it's pretty small. */ ep = sp->ep; if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS) msgq(sp, M_SYSERR, "Unable to lock recovery file"); if (!issync) { /* Save the recover file descriptor, and mail path. */ ep->rcv_fd = fd; if ((ep->rcv_mpath = strdup(mpath)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } cp_path = ep->rcv_path; } /* * XXX * We can't use stdio(3) here. The problem is that we may be using * fcntl(2), so if ANY file descriptor into the file is closed, the * lock is lost. So, we could never close the FILE *, even if we * dup'd the fd first. */ t = sp->frp->name; if ((p = strrchr(t, '/')) == NULL) p = t; else ++p; (void)time(&now); (void)gethostname(host, sizeof(host)); len = snprintf(buf, sizeof(buf), "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n%s\n\n", VI_FHEADER, t, /* Non-standard. */ VI_PHEADER, cp_path, /* Non-standard. */ "Reply-To: root", "From: root (OpenVi recovery program)", "To: ", pw->pw_name, "Subject: OpenVi saved the file ", p, "Precedence: bulk", /* For vacation(1). */ "Auto-Submitted: auto-generated"); if (len > sizeof(buf) - 1) goto lerr; if (write(fd, buf, len) != len) goto werr; len = snprintf(buf, sizeof(buf), "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n", "On ", ctime(&now), ", the user ", pw->pw_name, " was editing a file named ", t, " on the machine ", host, ", when it was saved for recovery. ", "You can recover most, if not all, of the changes ", "to this file using the -r option to ", bsd_getprogname(), ":\n\n\t", bsd_getprogname(), " -r ", t); if (len > sizeof(buf) - 1) { lerr: msgq(sp, M_ERR, "Recovery file buffer overrun"); goto err; } /* * Format the message. (Yes, I know it's silly.) * Requires that the message end in a . */ #define FMTCOLS 60 for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) { /* Check for a short length. */ if (len <= FMTCOLS) { t2 = t1 + (len - 1); goto wout; } /* Check for a required . */ t2 = strchr(t1, '\n'); if (t2 - t1 <= FMTCOLS) goto wout; /* Find the closest space, if any. */ for (t3 = t2; t2 > t1; --t2) if (*t2 == ' ') { if (t2 - t1 <= FMTCOLS) goto wout; t3 = t2; } t2 = t3; /* t2 points to the last character to display. */ wout: *t2++ = '\n'; /* t2 points one after the last character to display. */ if (write(fd, t1, t2 - t1) != t2 - t1) goto werr; } if (issync) { rcv_email(sp, fd); if (close(fd)) { werr: msgq(sp, M_SYSERR, "Recovery file"); goto err; } } return (0); err: if (!issync) ep->rcv_fd = -1; if (fd != -1) (void)close(fd); return (1); } /* * rcv_openat -- * Open a recovery file in the specified dir and lock it. * * PUBLIC: int rcv_openat(SCR *, int, const char *, int *) */ int rcv_openat(SCR *sp, int dfd, const char *name, int *locked) { struct stat sb; int fd, dummy; /* * If it's readable, it's recoverable. * Note: file_lock() sets the close on exec flag for us. */ fd = openat(dfd, name, O_RDONLY|O_NOFOLLOW|O_NONBLOCK); if (fd == -1) goto bad; /* * Real vi recovery files are created with mode 0600. * If not a regular file or the mode has changed, skip it. */ if (fstat(fd, &sb) == -1 || !S_ISREG(sb.st_mode) || (sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR)) goto bad; if (locked == NULL) locked = &dummy; switch ((*locked = file_lock(sp, NULL, NULL, fd, 0))) { case LOCK_FAILED: /* * XXX * Assume that a lock can't be acquired, but that we * should permit recovery anyway. If this is wrong, * and someone else is using the file, we're going to * die horribly. */ break; case LOCK_SUCCESS: break; case LOCK_UNAVAIL: /* If it's locked, it's live. */ goto bad; } return fd; bad: if (fd != -1) close(fd); return -1; } /* * people making love * never exactly the same * just like a snowflake * * rcv_list -- * List the files that can be recovered by this user. * * PUBLIC: int rcv_list(SCR *); */ int rcv_list(SCR *sp) { struct dirent *dp; struct stat sb; DIR *dirp; int fd; FILE *fp; int found; char *p, *t, file[PATH_MAX], path[PATH_MAX]; /* Open the recovery directory for reading. */ if (opts_empty(sp, O_RECDIR, 0)) return (1); p = O_STR(sp, O_RECDIR); if ((dirp = opendir(p)) == NULL) { msgq_str(sp, M_SYSERR, p, "recdir: %s"); return (1); } /* Read the directory. */ for (found = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, NULL)) == -1) continue; /* Check the headers. */ if ((fp = fdopen(fd, "r")) == NULL) { close(fd); continue; } if (fgets(file, sizeof(file), fp) == NULL || strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || (p = strchr(file, '\n')) == NULL || fgets(path, sizeof(path), fp) == NULL || strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || (t = strchr(path, '\n')) == NULL) { msgq_str(sp, M_ERR, dp->d_name, "%s: malformed recovery file"); goto next; } *p = *t = '\0'; /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && errno == ENOENT) { (void)unlinkat(dirfd(dirp), dp->d_name, 0); goto next; } /* Get the last modification time and display. */ (void)fstat(fd, &sb); (void)printf("%.24s: %s\n", ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1); found = 1; /* Close, discarding lock. */ next: (void)fclose(fp); } if (found == 0) (void)printf("%s: No files to recover\n", bsd_getprogname()); (void)closedir(dirp); return (0); } /* * rcv_read -- * Start a recovered file as the file to edit. * * PUBLIC: int rcv_read(SCR *, FREF *); */ int rcv_read(SCR *sp, FREF *frp) { struct dirent *dp; struct stat sb; DIR *dirp; EXF *ep; #ifdef _AIX struct st_timespec rec_mtim; #else struct timespec rec_mtim; #endif /* ifdef _AIX */ int fd, found, lck, requested, sv_fd; char *name, *p, *t, *rp, *recp, *pathp; char file[PATH_MAX], path[PATH_MAX], recpath[PATH_MAX]; if (opts_empty(sp, O_RECDIR, 0)) return (1); rp = O_STR(sp, O_RECDIR); if ((dirp = opendir(rp)) == NULL) { msgq_str(sp, M_SYSERR, rp, "%s"); return (1); } name = frp->name; sv_fd = -1; rec_mtim.tv_sec = rec_mtim.tv_nsec = 0; recp = pathp = NULL; for (found = requested = 0; (dp = readdir(dirp)) != NULL;) { if (strncmp(dp->d_name, "recover.", 8)) continue; if ((size_t)snprintf(recpath, sizeof(recpath), "%s/%s", rp, dp->d_name) >= sizeof(recpath)) continue; if ((fd = rcv_openat(sp, dirfd(dirp), dp->d_name, &lck)) == -1) continue; /* Check the headers. */ if (rcv_gets(file, sizeof(file), fd) == NULL || strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) || (p = strchr(file, '\n')) == NULL || rcv_gets(path, sizeof(path), fd) == NULL || strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) || (t = strchr(path, '\n')) == NULL) { msgq_str(sp, M_ERR, recpath, "%s: malformed recovery file"); goto next; } *p = *t = '\0'; ++found; /* * If the file doesn't exist, it's an orphaned recovery file, * toss it. * * XXX * This can occur if the backup file was deleted and we crashed * before deleting the email file. */ errno = 0; if (stat(path + sizeof(VI_PHEADER) - 1, &sb) && errno == ENOENT) { (void)unlink(dp->d_name); goto next; } /* Check the file name. */ if (strcmp(file + sizeof(VI_FHEADER) - 1, name)) goto next; ++requested; /* * If we've found more than one, take the most recent. */ (void)fstat(fd, &sb); if (recp == NULL || timespeccmp(&rec_mtim, &sb.st_mtim, <)) { p = recp; t = pathp; if ((recp = strdup(recpath)) == NULL) { msgq(sp, M_SYSERR, NULL); recp = p; goto next; } if ((pathp = strdup(path)) == NULL) { msgq(sp, M_SYSERR, NULL); free(recp); recp = p; pathp = t; goto next; } if (p != NULL) { free(p); free(t); } rec_mtim = sb.st_mtim; if (sv_fd != -1) (void)close(sv_fd); sv_fd = fd; } else next: (void)close(fd); } (void)closedir(dirp); if (recp == NULL) { msgq_str(sp, M_INFO, name, "No files named %s, readable by you, to recover"); return (1); } if (found) { if (requested > 1) msgq(sp, M_INFO, "There are older versions of this file for you to recover"); if (found > requested) msgq(sp, M_INFO, "There are other files for you to recover"); } /* * Create the FREF structure, start the btree file. * * XXX * file_init() is going to set ep->rcv_path. */ if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) { free(recp); free(pathp); (void)close(sv_fd); return (1); } /* * We keep an open lock on the file so that the recover option can * distinguish between files that are live and those that need to * be recovered. The lock is already acquired, just copy it. */ ep = sp->ep; ep->rcv_mpath = recp; ep->rcv_fd = sv_fd; if (lck != LOCK_SUCCESS) F_SET(frp, FR_UNLOCKED); /* We believe the file is recoverable. */ F_SET(ep, F_RCV_ON); return (0); } /* * rcv_copy -- * Copy a recovery file. */ int rcv_copy(SCR *sp, int wfd, char *fname) { int nr, nw, off, rfd; char buf[8 * 1024]; if ((rfd = open(fname, O_RDONLY)) == -1) goto err; while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) goto err; if (nr == 0) return (0); err: msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } /* * rcv_gets -- * Fgets(3) for a file descriptor. */ char * rcv_gets(char *buf, size_t len, int fd) { int nr; char *p; if ((nr = read(fd, buf, len - 1)) == -1) return (NULL); buf[nr] = '\0'; if ((p = strchr(buf, '\n')) == NULL) return (NULL); (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET); return (buf); } /* * rcv_mktemp -- * Paranoid make temporary file routine. */ int rcv_mktemp(SCR *sp, char *path, char *dname, int perms) { int fd; /* * !!! * We expect mkstemp(3) to set the permissions correctly. On * historic System V systems, mkstemp didn't. Do it here, on * GP's. This also protects us from users with stupid umasks. * * XXX * The variable perms should really be a mode_t. */ if ((fd = mkstemp(path)) == -1 || fchmod(fd, perms) == -1) { msgq_str(sp, M_SYSERR, dname, "%s"); if (fd != -1) { close(fd); unlink(path); fd = -1; } } return (fd); } /* * rcv_email -- * Send email. */ void rcv_email(SCR *sp, int fd) { struct stat sb; pid_t pid; /* * In secure mode, our pledge(2) includes neither "proc" * nor "exec". So simply skip sending the mail. * Later vi -r still works because rcv_mailfile() * already did all the necessary setup. */ if (O_ISSET(sp, O_SECURE)) return; if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb) == -1) msgq_str(sp, M_SYSERR, _PATH_SENDMAIL, "not sending email: %s"); else { /* * !!! * If you need to port this to a system that doesn't have * sendmail, the -t flag causes sendmail to read the message * for the recipients instead of specifying them some other * way. */ switch (pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); break; case 0: /* Sendmail. */ if (lseek(fd, 0, SEEK_SET) == -1) { msgq(sp, M_SYSERR, "lseek"); _exit(127); } if (fd != STDIN_FILENO) { if (dup2(fd, STDIN_FILENO) == -1) { msgq(sp, M_SYSERR, "dup2"); _exit(127); } close(fd); } execl(_PATH_SENDMAIL, "sendmail", "-t", (char *)NULL); msgq(sp, M_SYSERR, _PATH_SENDMAIL); _exit(127); default: /* Parent. */ while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) continue; break; } } } ================================================ FILE: common/screen.c ================================================ /* $OpenBSD: screen.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" /* * screen_init -- * Do the default initialization of an SCR structure. * * PUBLIC: int screen_init(GS *, SCR *, SCR **); */ int screen_init(GS *gp, SCR *orig, SCR **spp) { SCR *sp; size_t len; *spp = NULL; CALLOC_RET(orig, sp, 1, sizeof(SCR)); *spp = sp; /* INITIALIZED AT SCREEN CREATE. */ sp->id = ++gp->id; sp->refcnt = 1; sp->gp = gp; /* All ref the GS structure. */ sp->ccnt = 2; /* Anything > 1 */ /* * XXX * sp->defscroll is initialized by the opts_init() code because * we don't have the option information yet. */ TAILQ_INIT(&sp->tiq); /* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ if (orig == NULL) { sp->searchdir = NOTSET; } else { /* Alternate file name. */ if (orig->alt_name != NULL && (sp->alt_name = strdup(orig->alt_name)) == NULL) goto mem; /* Last executed at buffer. */ if (F_ISSET(orig, SC_AT_SET)) { F_SET(sp, SC_AT_SET); sp->at_lbuf = orig->at_lbuf; } /* Retain searching/substitution information. */ sp->searchdir = orig->searchdir == NOTSET ? NOTSET : FORWARD; if (orig->re != NULL && (sp->re = v_strdup(sp, orig->re, orig->re_len)) == NULL) goto mem; sp->re_len = orig->re_len; if (orig->subre != NULL && (sp->subre = v_strdup(sp, orig->subre, orig->subre_len)) == NULL) goto mem; sp->subre_len = orig->subre_len; if (orig->repl != NULL && (sp->repl = v_strdup(sp, orig->repl, orig->repl_len)) == NULL) goto mem; sp->repl_len = orig->repl_len; if (orig->newl_len) { len = orig->newl_len * sizeof(size_t); MALLOC(sp, sp->newl, len); if (sp->newl == NULL) { mem: msgq(orig, M_SYSERR, NULL); goto err; } sp->newl_len = orig->newl_len; sp->newl_cnt = orig->newl_cnt; memcpy(sp->newl, orig->newl, len); } if (opts_copy(orig, sp)) goto err; F_SET(sp, F_ISSET(orig, SC_EX | SC_VI)); } if (ex_screen_copy(orig, sp)) /* Ex. */ goto err; if (v_screen_copy(orig, sp)) /* Vi. */ goto err; *spp = sp; return (0); err: screen_end(sp); return (1); } /* * screen_end -- * Release a screen, no matter what had (and had not) been * initialized. * * PUBLIC: int screen_end(SCR *); */ int screen_end(SCR *sp) { int rval; SCR *tsp; /* If multiply referenced, just decrement the count and return. */ if (--sp->refcnt != 0) return (0); /* * Remove the screen from the displayed and hidden queues. * * If a created screen failed during initialization, it may not * be linked into a queue. */ TAILQ_FOREACH(tsp, &sp->gp->dq, q) { if (tsp == sp) { TAILQ_REMOVE(&sp->gp->dq, sp, q); break; } } TAILQ_FOREACH(tsp, &sp->gp->hq, q) { if (tsp == sp) { TAILQ_REMOVE(&sp->gp->hq, sp, q); break; } } /* The screen is no longer real. */ F_CLR(sp, SC_SCR_EX | SC_SCR_VI); rval = 0; if (v_screen_end(sp)) /* End vi. */ rval = 1; if (ex_screen_end(sp)) /* End ex. */ rval = 1; /* Free file names. */ { char **ap; if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { for (ap = sp->argv; *ap != NULL; ++ap) free(*ap); free(sp->argv); } } /* Free any text input. */ if (TAILQ_FIRST(&sp->tiq) != NULL) text_lfree(&sp->tiq); /* Free alternate file name. */ free(sp->alt_name); /* Free up search information. */ free(sp->re); if (F_ISSET(sp, SC_RE_SEARCH)) regfree(&sp->re_c); free(sp->subre); if (F_ISSET(sp, SC_RE_SUBST)) regfree(&sp->subre_c); free(sp->repl); free(sp->newl); /* Free all the options */ opts_free(sp); /* Free the screen itself. */ free(sp); return (rval); } /* * screen_next -- * Return the next screen in the queue. * * PUBLIC: SCR *screen_next(SCR *); */ SCR * screen_next(SCR *sp) { GS *gp; SCR *next; /* Try the display queue, without returning the current screen. */ gp = sp->gp; TAILQ_FOREACH(next, &gp->dq, q) if (next != sp) return (next); /* Try the hidden queue; if found, move screen to the display queue. */ if (!TAILQ_EMPTY(&gp->hq)) { next = TAILQ_FIRST(&gp->hq); TAILQ_REMOVE(&gp->hq, next, q); TAILQ_INSERT_HEAD(&gp->dq, next, q); return (next); } return (NULL); } ================================================ FILE: common/screen.h ================================================ /* $OpenBSD: screen.h,v 1.10 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)screen.h 10.24 (Berkeley) 7/19/96 */ /* * There are minimum values that vi has to have to display a screen. The row * minimum is fixed at 1 (the svi code can share a line between the text line * and the colon command/message line). Column calculation is a lot trickier. * For example, you have to have enough columns to display the line number, * not to mention guaranteeing that tabstop and shiftwidth values are smaller * than the current column value. It's simpler to have a fixed value and not * worry about it. */ #define MINIMUM_SCREEN_ROWS 1 #define MINIMUM_SCREEN_COLS 20 /* * SCR -- * The screen structure. To the extent possible, all screen information * is stored in the various private areas. The only information here * is used by global routines or is shared by too many screens. */ struct _scr { /* INITIALIZED AT SCREEN CREATE. */ TAILQ_ENTRY(_scr) q; /* Screens. */ int id; /* Screen id #. */ int refcnt; /* Reference count. */ GS *gp; /* Pointer to global area. */ SCR *nextdisp; /* Next display screen. */ SCR *ccl_parent; /* Colon command-line parent screen. */ EXF *ep; /* Screen's current EXF structure. */ FREF *frp; /* FREF being edited. */ char **argv; /* NULL terminated file name array. */ char **cargv; /* Current file name. */ unsigned long ccnt; /* Command count. */ unsigned long q_ccnt; /* Quit or ZZ command count. */ /* Screen's: */ size_t rows; /* 1-N: number of rows. */ size_t cols; /* 1-N: number of columns. */ size_t t_rows; /* 1-N: cur number of text rows. */ size_t t_maxrows; /* 1-N: max number of text rows. */ size_t t_minrows; /* 1-N: min number of text rows. */ size_t woff; /* 0-N: screen offset in frame. */ /* Cursor's: */ recno_t lno; /* 1-N: file line. */ size_t cno; /* 0-N: file character in line. */ size_t rcm; /* Vi: 0-N: Most attractive column. */ #define L_ADDED 0 /* Added lines. */ #define L_CHANGED 1 /* Changed lines. */ #define L_DELETED 2 /* Deleted lines. */ #define L_JOINED 3 /* Joined lines. */ #define L_MOVED 4 /* Moved lines. */ #define L_SHIFT 5 /* Shift lines. */ #define L_YANKED 6 /* Yanked lines. */ recno_t rptlchange; /* Ex/vi: last L_CHANGED lno. */ recno_t rptlines[L_YANKED + 1];/* Ex/vi: lines changed by last op. */ TEXTH tiq; /* Ex/vi: text input queue. */ SCRIPT *script; /* Vi: script mode information .*/ recno_t defscroll; /* Vi: ^D, ^U scroll information. */ /* Display character. */ CHAR_T cname[MAX_CHARACTER_COLUMNS + 1]; size_t clen; /* Length of display character. */ enum { /* Vi editor mode. */ SM_APPEND = 0, SM_CHANGE, SM_COMMAND, SM_INSERT, SM_REPLACE } showmode; void *ex_private; /* Ex private area. */ void *vi_private; /* Vi private area. */ /* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */ char *alt_name; /* Ex/vi: alternate file name. */ CHAR_T at_lbuf; /* Ex/vi: Last executed at buffer. */ /* Ex/vi: re_compile flags. */ #define RE_C_SEARCH 0x0002 /* Compile search replacement. */ #define RE_C_SILENT 0x0004 /* No error messages. */ #define RE_C_SUBST 0x0008 /* Compile substitute replacement. */ #define RE_C_TAG 0x0010 /* Compile ctag pattern. */ #define RE_WSTART "[[:<:]]" /* Ex/vi: not-in-word search pattern. */ #define RE_WSTOP "[[:>:]]" /* Ex/vi: flags to search routines. */ #define SEARCH_EOL 0x0002 /* Offset past EOL is okay. */ #define SEARCH_FILE 0x0004 /* Search the entire file. */ #define SEARCH_INCR 0x0008 /* Search incrementally. */ #define SEARCH_MSG 0x0010 /* Display search messages. */ #define SEARCH_PARSE 0x0020 /* Parse the search pattern. */ #define SEARCH_SET 0x0040 /* Set search direction. */ #define SEARCH_TAG 0x0080 /* Search for a tag pattern. */ #define SEARCH_WMSG 0x0100 /* Display search-wrapped messages. */ /* Ex/vi: RE information. */ dir_t searchdir; /* Last file search direction. */ regex_t re_c; /* Search RE: compiled form. */ char *re; /* Search RE: uncompiled form. */ size_t re_len; /* Search RE: uncompiled length. */ regex_t subre_c; /* Substitute RE: compiled form. */ char *subre; /* Substitute RE: uncompiled form. */ size_t subre_len; /* Substitute RE: uncompiled length). */ char *repl; /* Substitute replacement. */ size_t repl_len; /* Substitute replacement length.*/ size_t *newl; /* Newline offset array. */ size_t newl_len; /* Newline array size. */ size_t newl_cnt; /* Newlines in replacement. */ u_int8_t c_suffix; /* Edcompatible 'c' suffix value. */ u_int8_t g_suffix; /* Edcompatible 'g' suffix value. */ OPTION opts[O_OPTIONCOUNT]; /* Ex/vi: Options. */ /* * Screen flags. * * Editor screens. */ #define SC_EX 0x00000001 /* Ex editor. */ #define SC_VI 0x00000002 /* Vi editor. */ /* * Screen formatting flags, first major, then minor. * * SC_SCR_EX * Ex screen, i.e. cooked mode. * SC_SCR_VI * Vi screen, i.e. raw mode. * SC_SCR_EXWROTE * The editor had to write on the screen behind curses' back, and we can't * let curses change anything until the user agrees, e.g. entering the * commands :!utility followed by :set. We have to switch back into the * vi "editor" to read the user's command input, but we can't touch the * rest of the screen because it's known to be wrong. * SC_SCR_REFORMAT * The expected presentation of the lines on the screen have changed, * requiring that the intended screen lines be recalculated. Implies * SC_SCR_REDRAW. * SC_SCR_REDRAW * The screen doesn't correctly represent the file; repaint it. Note, * setting SC_SCR_REDRAW in the current window causes *all* windows to * be repainted. * SC_SCR_CENTER * If the current line isn't already on the screen, center it. * SC_SCR_TOP * If the current line isn't already on the screen, put it at the to@. */ #define SC_SCR_EX 0x00000004 /* Screen is in ex mode. */ #define SC_SCR_VI 0x00000008 /* Screen is in vi mode. */ #define SC_SCR_EXWROTE 0x00000010 /* Ex overwrite: see comment above. */ #define SC_SCR_REFORMAT 0x00000020 /* Reformat (refresh). */ #define SC_SCR_REDRAW 0x00000040 /* Refresh. */ #define SC_SCR_CENTER 0x00000080 /* Center the line if not visible. */ #define SC_SCR_TOP 0x00000100 /* Top the line if not visible. */ /* Screen/file changes. */ #define SC_EXIT 0x00000200 /* Exiting (not forced). */ #define SC_EXIT_FORCE 0x00000400 /* Exiting (forced). */ #define SC_FSWITCH 0x00000800 /* Switch underlying files. */ #define SC_SSWITCH 0x00001000 /* Switch screens. */ #define SC_ARGNOFREE 0x00002000 /* Argument list wasn't allocated. */ #define SC_ARGRECOVER 0x00004000 /* Argument list is recovery files. */ #define SC_AT_SET 0x00008000 /* Last at buffer set. */ #define SC_COMEDIT 0x00010000 /* Colon command-line edit window. */ #define SC_EX_GLOBAL 0x00020000 /* Ex: executing a global command. */ #define SC_EX_SILENT 0x00040000 /* Ex: batch script. */ #define SC_EX_WAIT_NO 0x00080000 /* Ex: don't wait for the user. */ #define SC_EX_WAIT_YES 0x00100000 /* Ex: do wait for the user. */ #define SC_READONLY 0x00200000 /* Persistent readonly state. */ #define SC_RE_SEARCH 0x00400000 /* Search RE has been compiled. */ #define SC_RE_SUBST 0x00800000 /* Substitute RE has been compiled. */ #define SC_SCRIPT 0x01000000 /* Shell script window. */ #define SC_STATUS 0x02000000 /* Welcome message. */ #define SC_STATUS_CNT 0x04000000 /* Welcome message plus file count. */ #define SC_TINPUT 0x08000000 /* Doing text input. */ #define SC_TINPUT_INFO 0x10000000 /* Doing text input on info line. */ u_int32_t flags; }; ================================================ FILE: common/search.c ================================================ /* $OpenBSD: search.c,v 1.14 2022/12/10 16:06:18 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t; static void search_msg(SCR *, smsg_t); static int search_init(SCR *, dir_t, char *, size_t, char **, unsigned int); /* * search_init -- * Set up a search. */ static int search_init(SCR *sp, dir_t dir, char *ptrn, size_t plen, char **epp, unsigned int flags) { recno_t lno; int delim; char *p, *t; /* If the file is empty, it's a fast search. */ if (sp->lno <= 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); return (1); } } if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */ /* * Use the saved pattern if no pattern specified, or if only * one or two delimiter characters specified. * * !!! * Historically, only the pattern itself was saved, vi didn't * preserve addressing or delta information. */ if (ptrn == NULL) goto prev; if (plen == 1) { if (epp != NULL) *epp = ptrn + 1; goto prev; } if (ptrn[0] == ptrn[1]) { if (epp != NULL) *epp = ptrn + 2; /* Complain if we don't have a previous pattern. */ prev: if (sp->re == NULL) { search_msg(sp, S_NOPREV); return (1); } /* Re-compile the search pattern if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * Set the delimiter, and move forward to the terminating * delimiter, handling escaped delimiters. * * QUOTING NOTE: * Only discard an escape character if it escapes a delimiter. */ for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) { if (--plen == 0 || p[0] == delim) { if (plen != 0) ++p; break; } if (plen > 1 && p[0] == '\\') { if (p[1] == delim) { ++p; --plen; } else if (p[1] == '\\') { *t++ = *p++; --plen; } } } if (epp != NULL) *epp = p; plen = t - ptrn; } /* Compile the RE. */ if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH | (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) | (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0))) return (1); /* Set the search direction. */ if (LF_ISSET(SEARCH_SET)) sp->searchdir = dir; return (0); } /* * f_search -- * Do a forward search. * * PUBLIC: int f_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int); */ int f_search(SCR *sp, MARK *fm, MARK *rm, char *ptrn, size_t plen, char **eptrn, unsigned int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, len; int cnt, eval, rval, wrapped = 0; char *l; if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags)) return (1); if (LF_ISSET(SEARCH_FILE)) { lno = 1; coff = 0; } else { if (db_get(sp, fm->lno, DBG_FATAL, &l, &len)) return (1); lno = fm->lno; /* * If doing incremental search, start searching at the previous * column, so that we search a minimal distance and still match * special patterns, e.g., \< for beginning of a word. * * Otherwise, start searching immediately after the cursor. If * at the end of the line, start searching on the next line. * This is incompatible (read bug fix) with the historic vi -- * searches for the '$' pattern never moved forward, and the * "-t foo" didn't work if the 'f' was the first character in * the file. */ if (LF_ISSET(SEARCH_INCR)) { if ((coff = fm->cno) != 0) --coff; } else if (fm->cno + 1 >= len) { coff = 0; lno = fm->lno + 1; if (db_get(sp, lno, 0, &l, &len)) { if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); return (1); } lno = 1; wrapped = 1; } } else coff = fm->cno + 1; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1;; ++lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno > fm->lno) || db_get(sp, lno, 0, &l, &len)) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EOF); break; } lno = 0; wrapped = 1; continue; } /* If already at EOL, just keep going. */ if (len != 0 && coff == len) continue; /* Set the termination. */ match[0].rm_so = coff; match[0].rm_eo = len; /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); rm->lno = lno; rm->cno = match[0].rm_so; /* * If a change command, it's possible to move beyond the end * of a line. Historic vi generally got this wrong (e.g. try * "c?$"). Not all that sure this gets it right, there * are lots of strange cases. */ if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len) rm->cno = len != 0 ? len - 1 : 0; rval = 0; break; } if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * b_search -- * Do a backward search. * * PUBLIC: int b_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int); */ int b_search(SCR *sp, MARK *fm, MARK *rm, char *ptrn, size_t plen, char **eptrn, unsigned int flags) { busy_t btype; recno_t lno; regmatch_t match[1]; size_t coff, last, len; int cnt, eval, rval, wrapped; char *l; if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags)) return (1); /* * If doing incremental search, set the "starting" position past the * current column, so that we search a minimal distance and still * match special patterns, e.g., \> for the end of a word. This is * safe when the cursor is at the end of a line because we only use * it for comparison with the location of the match. * * Otherwise, start searching immediately before the cursor. If in * the first column, start search on the previous line. */ if (LF_ISSET(SEARCH_INCR)) { lno = fm->lno; coff = fm->cno + 1; } else { if (fm->cno == 0) { if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); return (1); } lno = fm->lno - 1; } else lno = fm->lno; coff = fm->cno; } btype = BUSY_ON; for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) { if (cnt-- == 0) { if (INTERRUPTED(sp)) break; if (LF_ISSET(SEARCH_MSG)) { search_busy(sp, btype); btype = BUSY_UPDATE; } cnt = INTERRUPT_CHECK; } if ((wrapped && lno < fm->lno) || lno == 0) { if (wrapped) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_NOTFOUND); break; } if (!O_ISSET(sp, O_WRAPSCAN)) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_SOF); break; } if (db_last(sp, &lno)) break; if (lno == 0) { if (LF_ISSET(SEARCH_MSG)) search_msg(sp, S_EMPTY); break; } ++lno; wrapped = 1; continue; } if (db_get(sp, lno, 0, &l, &len)) break; /* Set the termination. */ match[0].rm_so = 0; match[0].rm_eo = len; /* Search the line. */ eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND); if (eval == REG_NOMATCH) continue; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); break; } /* Check for a match starting past the cursor. */ if (coff != 0 && match[0].rm_so >= coff) continue; /* Warn if the search wrapped. */ if (wrapped && LF_ISSET(SEARCH_WMSG)) search_msg(sp, S_WRAP); /* * We now have the first match on the line. Step through the * line character by character until find the last acceptable * match. This is painful, we need a better interface to regex * to make this work. */ for (;;) { last = match[0].rm_so++; if (match[0].rm_so >= len) break; match[0].rm_eo = len; eval = regexec(&sp->re_c, l, 1, match, (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND); if (eval == REG_NOMATCH) break; if (eval != 0) { if (LF_ISSET(SEARCH_MSG)) re_error(sp, eval, &sp->re_c); else (void)sp->gp->scr_bell(sp); goto err; } if (coff && match[0].rm_so >= coff) break; } rm->lno = lno; /* See comment in f_search(). */ if (!LF_ISSET(SEARCH_EOL) && last >= len) rm->cno = len != 0 ? len - 1 : 0; else rm->cno = last; rval = 0; break; } err: if (LF_ISSET(SEARCH_MSG)) search_busy(sp, BUSY_OFF); return (rval); } /* * search_msg -- * Display one of the search messages. */ static void search_msg(SCR *sp, smsg_t msg) { switch (msg) { case S_EMPTY: msgq(sp, M_ERR, "File empty; nothing to search"); break; case S_EOF: msgq(sp, M_ERR, "Reached end-of-file without finding the pattern"); break; case S_NOPREV: msgq(sp, M_ERR, "No previous search pattern"); break; case S_NOTFOUND: msgq(sp, M_ERR, "Pattern not found"); break; case S_SOF: msgq(sp, M_ERR, "Reached top-of-file without finding the pattern"); break; case S_WRAP: msgq(sp, M_ERR, "Search wrapped"); break; default: abort(); } } /* * search_busy -- * Put up the busy searching message. * * PUBLIC: void search_busy(SCR *, busy_t); */ void search_busy(SCR *sp, busy_t btype) { sp->gp->scr_busy(sp, "Searching...", btype); } ================================================ FILE: common/seq.c ================================================ /* $OpenBSD: seq.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "common.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) /* * seq_set -- * Internal version to enter a sequence. * * PUBLIC: int seq_set(SCR *, CHAR_T *, * PUBLIC: size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int); */ int seq_set(SCR *sp, CHAR_T *name, size_t nlen, CHAR_T *input, size_t ilen, CHAR_T *output, size_t olen, seq_t stype, int flags) { CHAR_T *p; SEQ *lastqp, *qp; int sv_errno; /* * An input string must always be present. The output string * can be NULL, when set internally, that's how we throw away * input. * * Just replace the output field if the string already set. */ if ((qp = seq_find(sp, &lastqp, NULL, input, ilen, stype, NULL)) != NULL) { if (LF_ISSET(SEQ_NOOVERWRITE)) return (0); if (output == NULL || olen == 0) { p = NULL; olen = 0; } else if ((p = v_strdup(sp, output, olen)) == NULL) { sv_errno = errno; goto mem1; } free(qp->output); qp->olen = olen; qp->output = p; return (0); } /* Allocate and initialize SEQ structure. */ CALLOC(sp, qp, 1, sizeof(SEQ)); if (qp == NULL) { sv_errno = errno; goto mem1; } /* Name. */ if (name == NULL || nlen == 0) qp->name = NULL; else if ((qp->name = v_strdup(sp, name, nlen)) == NULL) { sv_errno = errno; goto mem2; } qp->nlen = nlen; /* Input. */ if ((qp->input = v_strdup(sp, input, ilen)) == NULL) { sv_errno = errno; goto mem3; } qp->ilen = ilen; /* Output. */ if (output == NULL) { qp->output = NULL; olen = 0; } else if ((qp->output = v_strdup(sp, output, olen)) == NULL) { sv_errno = errno; free(qp->input); mem3: free(qp->name); mem2: free(qp); mem1: errno = sv_errno; msgq(sp, M_SYSERR, NULL); return (1); } qp->olen = olen; /* Type, flags. */ qp->stype = stype; qp->flags = flags; /* Link into the chain. */ if (lastqp == NULL) { LIST_INSERT_HEAD(&sp->gp->seqq, qp, q); } else { LIST_INSERT_AFTER(lastqp, qp, q); } /* Set the fast lookup bit. */ if (qp->input[0] < MAX_BIT_SEQ) bit_set(sp->gp->seqb, qp->input[0]); return (0); } /* * seq_delete -- * Delete a sequence. * * PUBLIC: int seq_delete(SCR *, CHAR_T *, size_t, seq_t); */ int seq_delete(SCR *sp, CHAR_T *input, size_t ilen, seq_t stype) { SEQ *qp; if ((qp = seq_find(sp, NULL, NULL, input, ilen, stype, NULL)) == NULL) return (1); return (seq_mdel(qp)); } /* * seq_mdel -- * Delete a map entry, without lookup. * * PUBLIC: int seq_mdel(SEQ *); */ int seq_mdel(SEQ *qp) { LIST_REMOVE(qp, q); free(qp->name); free(qp->input); free(qp->output); free(qp); return (0); } /* * seq_find -- * Search the sequence list for a match to a buffer, if ispartial * isn't NULL, partial matches count. * * PUBLIC: SEQ *seq_find * PUBLIC:(SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *); */ SEQ * seq_find(SCR *sp, SEQ **lastqp, EVENT *e_input, CHAR_T *c_input, size_t ilen, seq_t stype, int *ispartialp) { SEQ *lqp, *qp; int diff; /* * Ispartialp is a location where we return if there was a * partial match, i.e. if the string were extended it might * match something. * * XXX * Overload the meaning of ispartialp; only the terminal key * search doesn't want the search limited to complete matches, * i.e. ilen may be longer than the match. */ if (ispartialp != NULL) *ispartialp = 0; for (lqp = NULL, qp = LIST_FIRST(&sp->gp->seqq); qp != NULL; lqp = qp, qp = LIST_NEXT(qp, q)) { /* * Fast checks on the first character and type, and then * a real comparison. */ if (e_input == NULL) { if (qp->input[0] > c_input[0]) break; if (qp->input[0] < c_input[0] || qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; diff = memcmp(qp->input, c_input, MINIMUM(qp->ilen, ilen)); } else { if (qp->input[0] > e_input->e_c) break; if (qp->input[0] < e_input->e_c || qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; diff = e_memcmp(qp->input, e_input, MINIMUM(qp->ilen, ilen)); } if (diff > 0) break; if (diff < 0) continue; /* * If the entry is the same length as the string, return a * match. If the entry is shorter than the string, return a * match if called from the terminal key routine. Otherwise, * keep searching for a complete match. */ if (qp->ilen <= ilen) { if (qp->ilen == ilen || ispartialp != NULL) { if (lastqp != NULL) *lastqp = lqp; return (qp); } continue; } /* * If the entry longer than the string, return partial match * if called from the terminal key routine. Otherwise, no * match. */ if (ispartialp != NULL) *ispartialp = 1; break; } if (lastqp != NULL) *lastqp = lqp; return (NULL); } /* * seq_close -- * Discard all sequences. * * PUBLIC: void seq_close(GS *); */ void seq_close(GS *gp) { SEQ *qp; while ((qp = LIST_FIRST(&gp->seqq)) != NULL) { free(qp->name); free(qp->input); free(qp->output); LIST_REMOVE(qp, q); free(qp); } } /* * seq_dump -- * Display the sequence entries of a specified type. * * PUBLIC: int seq_dump(SCR *, seq_t, int); */ int seq_dump(SCR *sp, seq_t stype, int isname) { CHAR_T *p; GS *gp; SEQ *qp; int cnt, len, olen; cnt = 0; gp = sp->gp; LIST_FOREACH(qp, &gp->seqq, q) { if (stype != qp->stype || F_ISSET(qp, SEQ_FUNCMAP)) continue; ++cnt; for (p = qp->input, olen = qp->ilen, len = 0; olen > 0; --olen, ++p) len += ex_puts(sp, KEY_NAME(sp, *p)); for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;) len -= ex_puts(sp, " "); if (qp->output != NULL) for (p = qp->output, olen = qp->olen, len = 0; olen > 0; --olen, ++p) len += ex_puts(sp, KEY_NAME(sp, *p)); else len = 0; if (isname && qp->name != NULL) { for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;) len -= ex_puts(sp, " "); for (p = qp->name, olen = qp->nlen; olen > 0; --olen, ++p) (void)ex_puts(sp, KEY_NAME(sp, *p)); } (void)ex_puts(sp, "\n"); } return (cnt); } /* * seq_save -- * Save the sequence entries to a file. * * PUBLIC: int seq_save(SCR *, FILE *, char *, seq_t); */ int seq_save(SCR *sp, FILE *fp, char *prefix, seq_t stype) { CHAR_T *p; SEQ *qp; size_t olen; int ch; /* Write a sequence command for all keys the user defined. */ LIST_FOREACH(qp, &sp->gp->seqq, q) { if (stype != qp->stype || !F_ISSET(qp, SEQ_USERDEF)) continue; if (prefix) (void)fprintf(fp, "%s", prefix); for (p = qp->input, olen = qp->ilen; olen > 0; --olen) { ch = *p++; if (ch == CH_LITERAL || ch == '|' || isblank(ch) || KEY_VAL(sp, ch) == K_NL) (void)putc(CH_LITERAL, fp); (void)putc(ch, fp); } (void)putc(' ', fp); if (qp->output != NULL) for (p = qp->output, olen = qp->olen; olen > 0; --olen) { ch = *p++; if (ch == CH_LITERAL || ch == '|' || KEY_VAL(sp, ch) == K_NL) (void)putc(CH_LITERAL, fp); (void)putc(ch, fp); } (void)putc('\n', fp); } return (0); } /* * e_memcmp -- * Compare a string of EVENT's to a string of CHAR_T's. * * PUBLIC: int e_memcmp(CHAR_T *, EVENT *, size_t); */ int e_memcmp(CHAR_T *p1, EVENT *ep, size_t n) { if (n != 0) { do { if (*p1++ != ep->e_c) return (*--p1 - ep->e_c); ++ep; } while (--n != 0); } return (0); } ================================================ FILE: common/seq.h ================================================ /* $OpenBSD: seq.h,v 1.5 2016/05/27 09:18:11 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)seq.h 10.3 (Berkeley) 3/6/96 */ /* * Map and abbreviation structures. * * The map structure is doubly linked list, sorted by input string and by * input length within the string. (The latter is necessary so that short * matches will happen before long matches when the list is searched.) * Additionally, there is a bitmap which has bits set if there are entries * starting with the corresponding character. This keeps us from walking * the list unless it's necessary. * * The name and the output fields of a SEQ can be empty, i.e. NULL. * Only the input field is required. * * XXX * The fast-lookup bits are never turned off -- users don't usually unmap * things, though, so it's probably not a big deal. */ struct _seq { LIST_ENTRY(_seq) q; /* Linked list of all sequences. */ seq_t stype; /* Sequence type. */ CHAR_T *name; /* Sequence name (if any). */ size_t nlen; /* Name length. */ CHAR_T *input; /* Sequence input keys. */ size_t ilen; /* Input keys length. */ CHAR_T *output; /* Sequence output keys. */ size_t olen; /* Output keys length. */ #define SEQ_FUNCMAP 0x01 /* If unresolved function key. */ #define SEQ_NOOVERWRITE 0x02 /* Don't replace existing entry. */ #define SEQ_SCREEN 0x04 /* If screen specific. */ #define SEQ_USERDEF 0x08 /* If user defined. */ u_int8_t flags; }; ================================================ FILE: common/util.c ================================================ /* $OpenBSD: util.c,v 1.17 2018/07/11 06:39:23 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "common.h" #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) /* * binc -- * Increase the size of a buffer. * * PUBLIC: void *binc(SCR *, void *, size_t *, size_t); */ void * binc(SCR *sp, void *bp, size_t *bsizep, size_t min) { size_t csize; /* If already larger than the minimum, just return. */ if (min && *bsizep >= min) return (bp); csize = *bsizep + MAXIMUM(min, 256); REALLOC(sp, bp, csize); if (bp == NULL) { *bsizep = 0; return (NULL); } /* * Memory is guaranteed to be zero-filled, various parts of * nvi depend on this. */ memset((char *)bp + *bsizep, 0, csize - *bsizep); *bsizep = csize; return (bp); } /* * nonblank -- * Set the column number of the first non-blank character * including or after the starting column. On error, set * the column to 0, it's safest. * * PUBLIC: int nonblank(SCR *, recno_t, size_t *); */ int nonblank(SCR *sp, recno_t lno, size_t *cnop) { char *p; size_t cnt, len, off; int isempty; /* Default. */ off = *cnop; *cnop = 0; /* Get the line, succeeding in an empty file. */ if (db_eget(sp, lno, &p, &len, &isempty)) return (!isempty); /* Set the offset. */ if (len == 0 || off >= len) return (0); for (cnt = off, p = &p[off], len -= off; len && isblank(*p); ++cnt, ++p, --len); /* Set the return. */ *cnop = len ? cnt : cnt - 1; return (0); } /* * v_strdup -- * Strdup for wide character strings with an associated length. * * PUBLIC: CHAR_T *v_strdup(SCR *, const CHAR_T *, size_t); */ CHAR_T * v_strdup(SCR *sp, const CHAR_T *str, size_t len) { CHAR_T *copy; MALLOC(sp, copy, len + 1); if (copy == NULL) return (NULL); memcpy(copy, str, len * sizeof(CHAR_T)); copy[len] = '\0'; return (copy); } /* * nget_uslong -- * Get an unsigned long, checking for overflow. * * PUBLIC: enum nresult nget_uslong(unsigned long *, const char *, char **, int); */ enum nresult nget_uslong(unsigned long *valp, const char *p, char **endp, int base) { errno = 0; *valp = strtoul(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE && *valp == ULONG_MAX) return (NUM_OVER); return (NUM_ERR); } /* * nget_slong -- * Convert a signed long, checking for overflow and underflow. * * PUBLIC: enum nresult nget_slong(long *, const char *, char **, int); */ enum nresult nget_slong(long *valp, const char *p, char **endp, int base) { errno = 0; *valp = strtol(p, endp, base); if (errno == 0) return (NUM_OK); if (errno == ERANGE) { if (*valp == LONG_MAX) return (NUM_OVER); if (*valp == LONG_MIN) return (NUM_UNDER); } return (NUM_ERR); } #ifdef DEBUG # include /* * TRACE -- * debugging trace routine. * * PUBLIC: void TRACE(SCR *, const char *, ...); */ void TRACE(SCR *sp, const char *fmt, ...) { FILE *tfp; va_list ap; if ((tfp = sp->gp->tracefp) == NULL) return; va_start(ap, fmt); (void)vfprintf(tfp, fmt, ap); fflush(tfp); va_end(ap); (void)fflush(tfp); } #endif /* ifdef DEBUG */ ================================================ FILE: db/btree/bt_close.c ================================================ /* $OpenBSD: bt_close.c,v 1.11 2021/10/24 10:05:22 jsg Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "btree.h" static int bt_meta(BTREE *); /* * BT_CLOSE -- Close a btree. * * Parameters: * dbp: pointer to access method * * Returns: * RET_ERROR, RET_SUCCESS */ int __bt_close(DB *dbp) { BTREE *t; int fd; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Sync the tree. */ if (__bt_sync(dbp, 0) == RET_ERROR) return (RET_ERROR); /* Close the memory pool. */ if (mpool_close(t->bt_mp) == RET_ERROR) return (RET_ERROR); /* Free random memory. */ if (t->bt_cursor.key.data != NULL) { free(t->bt_cursor.key.data); t->bt_cursor.key.size = 0; t->bt_cursor.key.data = NULL; } if (t->bt_rkey.data) { free(t->bt_rkey.data); t->bt_rkey.size = 0; t->bt_rkey.data = NULL; } if (t->bt_rdata.data) { free(t->bt_rdata.data); t->bt_rdata.size = 0; t->bt_rdata.data = NULL; } fd = t->bt_fd; free(t); free(dbp); return (close(fd) ? RET_ERROR : RET_SUCCESS); } /* * BT_SYNC -- sync the btree to disk. * * Parameters: * dbp: pointer to access method * * Returns: * RET_SUCCESS, RET_ERROR. */ int __bt_sync(const DB *dbp, unsigned int flags) { BTREE *t; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Sync doesn't currently take any flags. */ if (flags != 0) { errno = EINVAL; return (RET_ERROR); } if (F_ISSET(t, B_INMEM | B_RDONLY) || !F_ISSET(t, B_MODIFIED)) return (RET_SUCCESS); if (F_ISSET(t, B_METADIRTY) && bt_meta(t) == RET_ERROR) return (RET_ERROR); if ((status = mpool_sync(t->bt_mp)) == RET_SUCCESS) F_CLR(t, B_MODIFIED); return (status); } /* * BT_META -- write the tree meta data to disk. * * Parameters: * t: tree * * Returns: * RET_ERROR, RET_SUCCESS */ static int bt_meta(BTREE *t) { BTMETA m; void *p; if ((p = mpool_get(t->bt_mp, P_META, 0)) == NULL) return (RET_ERROR); /* Fill in metadata. */ m.magic = BTREEMAGIC; m.version = BTREEVERSION; m.psize = t->bt_psize; m.free = t->bt_free; m.nrecs = t->bt_nrecs; m.flags = F_ISSET(t, SAVEMETA); memmove(p, &m, sizeof(BTMETA)); mpool_put(t->bt_mp, p, MPOOL_DIRTY); return (RET_SUCCESS); } ================================================ FILE: db/btree/bt_conv.c ================================================ /* $OpenBSD: bt_conv.c,v 1.10 2015/01/16 16:48:51 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include "btree.h" static void mswap(PAGE *); /* * __BT_BPGIN, __BT_BPGOUT -- * Convert host-specific number layout to/from the host-independent * format stored on disk. * * Parameters: * t: tree * pg: page number * h: page to convert */ void __bt_pgin(void *t, pgno_t pg, void *pp) { PAGE *h; indx_t i, top; unsigned char flags; char *p; if (!F_ISSET(((BTREE *)t), B_NEEDSWAP)) return; if (pg == P_META) { mswap(pp); return; } h = pp; M_32_SWAP(h->pgno); M_32_SWAP(h->prevpg); M_32_SWAP(h->nextpg); M_32_SWAP(h->flags); M_16_SWAP(h->lower); M_16_SWAP(h->upper); top = NEXTINDEX(h); if ((h->flags & P_TYPE) == P_BINTERNAL) for (i = 0; i < top; i++) { M_16_SWAP(h->linp[i]); p = (char *)GETBINTERNAL(h, i); P_32_SWAP(p); p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(pgno_t); if (*(unsigned char *)p & P_BIGKEY) { p += sizeof(unsigned char); P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } } else if ((h->flags & P_TYPE) == P_BLEAF) for (i = 0; i < top; i++) { M_16_SWAP(h->linp[i]); p = (char *)GETBLEAF(h, i); P_32_SWAP(p); p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(u_int32_t); flags = *(unsigned char *)p; if (flags & (P_BIGKEY | P_BIGDATA)) { p += sizeof(unsigned char); if (flags & P_BIGKEY) { P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } if (flags & P_BIGDATA) { p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } } } } void __bt_pgout(void *t, pgno_t pg, void *pp) { PAGE *h; indx_t i, top; unsigned char flags; char *p; if (!F_ISSET(((BTREE *)t), B_NEEDSWAP)) return; if (pg == P_META) { mswap(pp); return; } h = pp; top = NEXTINDEX(h); if ((h->flags & P_TYPE) == P_BINTERNAL) for (i = 0; i < top; i++) { p = (char *)GETBINTERNAL(h, i); P_32_SWAP(p); p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(pgno_t); if (*(unsigned char *)p & P_BIGKEY) { p += sizeof(unsigned char); P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } M_16_SWAP(h->linp[i]); } else if ((h->flags & P_TYPE) == P_BLEAF) for (i = 0; i < top; i++) { p = (char *)GETBLEAF(h, i); P_32_SWAP(p); p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(u_int32_t); flags = *(unsigned char *)p; if (flags & (P_BIGKEY | P_BIGDATA)) { p += sizeof(unsigned char); if (flags & P_BIGKEY) { P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } if (flags & P_BIGDATA) { p += sizeof(u_int32_t); P_32_SWAP(p); p += sizeof(pgno_t); P_32_SWAP(p); } } M_16_SWAP(h->linp[i]); } M_32_SWAP(h->pgno); M_32_SWAP(h->prevpg); M_32_SWAP(h->nextpg); M_32_SWAP(h->flags); M_16_SWAP(h->lower); M_16_SWAP(h->upper); } /* * MSWAP -- Actually swap the bytes on the meta page. * * Parameters: * p: page to convert */ static void mswap(PAGE *pg) { char *p; p = (char *)pg; P_32_SWAP(p); /* magic */ p += sizeof(u_int32_t); P_32_SWAP(p); /* version */ p += sizeof(u_int32_t); P_32_SWAP(p); /* psize */ p += sizeof(u_int32_t); P_32_SWAP(p); /* free */ p += sizeof(u_int32_t); P_32_SWAP(p); /* nrecs */ p += sizeof(u_int32_t); P_32_SWAP(p); /* flags */ p += sizeof(u_int32_t); (void)p; } ================================================ FILE: db/btree/bt_debug.c ================================================ /* $OpenBSD: bt_debug.c,v 1.10 2015/01/16 16:48:51 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "btree.h" #ifdef DEBUG /* * BT_DUMP -- Dump the tree * * Parameters: * dbp: pointer to the DB */ void __bt_dump(DB *dbp) { BTREE *t; PAGE *h; pgno_t i; char *sep; t = dbp->internal; (void)fprintf(stderr, "%s: pgsz %u", F_ISSET(t, B_INMEM) ? "memory" : "disk", t->bt_psize); if (F_ISSET(t, R_RECNO)) (void)fprintf(stderr, " keys %u", t->bt_nrecs); # undef X # define X(flag, name) \ if (F_ISSET(t, flag)) { \ (void)fprintf(stderr, "%s%s", sep, name); \ sep = ", "; \ } if (t->flags != 0) { sep = " flags ("; X(R_FIXLEN, "FIXLEN"); X(B_INMEM, "INMEM"); X(B_NODUPS, "NODUPS"); X(B_RDONLY, "RDONLY"); X(R_RECNO, "RECNO"); X(B_METADIRTY,"METADIRTY"); (void)fprintf(stderr, ")\n"); } # undef X for (i = P_ROOT; (h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN)) != NULL; ++i) __bt_dpage(h); } /* * BT_DMPAGE -- Dump the meta page * * Parameters: * h: pointer to the PAGE */ void __bt_dmpage(PAGE *h) { BTMETA *m; char *sep; m = (BTMETA *)h; (void)fprintf(stderr, "magic %x\n", m->magic); (void)fprintf(stderr, "version %u\n", m->version); (void)fprintf(stderr, "psize %u\n", m->psize); (void)fprintf(stderr, "free %u\n", m->free); (void)fprintf(stderr, "nrecs %u\n", m->nrecs); (void)fprintf(stderr, "flags %u", m->flags); # undef X # define X(flag, name) \ if (m->flags & flag) { \ (void)fprintf(stderr, "%s%s", sep, name); \ sep = ", "; \ } if (m->flags) { sep = " ("; X(B_NODUPS, "NODUPS"); X(R_RECNO, "RECNO"); (void)fprintf(stderr, ")"); } } /* * BT_DNPAGE -- Dump the page * * Parameters: * n: page number to dump. */ void __bt_dnpage(DB *dbp, pgno_t pgno) { BTREE *t; PAGE *h; t = dbp->internal; if ((h = mpool_get(t->bt_mp, pgno, MPOOL_IGNOREPIN)) != NULL) __bt_dpage(h); } /* * BT_DPAGE -- Dump the page * * Parameters: * h: pointer to the PAGE */ void __bt_dpage(PAGE *h) { BINTERNAL *bi; BLEAF *bl; RINTERNAL *ri; RLEAF *rl; indx_t cur, top; char *sep; (void)fprintf(stderr, " page %u: (", h->pgno); # undef X # define X(flag, name) \ if (h->flags & flag) { \ (void)fprintf(stderr, "%s%s", sep, name); \ sep = ", "; \ } sep = ""; X(P_BINTERNAL, "BINTERNAL") /* types */ X(P_BLEAF, "BLEAF") X(P_RINTERNAL, "RINTERNAL") /* types */ X(P_RLEAF, "RLEAF") X(P_OVERFLOW, "OVERFLOW") X(P_PRESERVE, "PRESERVE"); (void)fprintf(stderr, ")\n"); # undef X (void)fprintf(stderr, "\tprev %2u next %2u", h->prevpg, h->nextpg); if (h->flags & P_OVERFLOW) return; top = NEXTINDEX(h); (void)fprintf(stderr, " lower %3d upper %3d nextind %d\n", h->lower, h->upper, top); for (cur = 0; cur < top; cur++) { (void)fprintf(stderr, "\t[%03d] %4d ", cur, h->linp[cur]); switch (h->flags & P_TYPE) { case P_BINTERNAL: bi = GETBINTERNAL(h, cur); (void)fprintf(stderr, "size %03d pgno %03d", bi->ksize, bi->pgno); if (bi->flags & P_BIGKEY) (void)fprintf(stderr, " (indirect)"); else if (bi->ksize) (void)fprintf(stderr, " {%.*s}", (int)bi->ksize, bi->bytes); break; case P_RINTERNAL: ri = GETRINTERNAL(h, cur); (void)fprintf(stderr, "entries %03d pgno %03d", ri->nrecs, ri->pgno); break; case P_BLEAF: bl = GETBLEAF(h, cur); if (bl->flags & P_BIGKEY) (void)fprintf(stderr, "big key page %u size %u/", *(pgno_t *)bl->bytes, *(u_int32_t *)(bl->bytes + sizeof(pgno_t))); else if (bl->ksize) (void)fprintf(stderr, "%s/", bl->bytes); if (bl->flags & P_BIGDATA) (void)fprintf(stderr, "big data page %u size %u", *(pgno_t *)(bl->bytes + bl->ksize), *(u_int32_t *)(bl->bytes + bl->ksize + sizeof(pgno_t))); else if (bl->dsize) (void)fprintf(stderr, "%.*s", (int)bl->dsize, bl->bytes + bl->ksize); break; case P_RLEAF: rl = GETRLEAF(h, cur); if (rl->flags & P_BIGDATA) (void)fprintf(stderr, "big data page %u size %u", *(pgno_t *)rl->bytes, *(u_int32_t *)(rl->bytes + sizeof(pgno_t))); else if (rl->dsize) (void)fprintf(stderr, "%.*s", (int)rl->dsize, rl->bytes); break; } (void)fprintf(stderr, "\n"); } } #endif /* ifdef DEBUG */ #ifdef STATISTICS /* * BT_STAT -- Gather/print the tree statistics * * Parameters: * dbp: pointer to the DB */ void __bt_stat(DB *dbp) { extern unsigned long bt_cache_hit, bt_cache_miss, bt_pfxsaved, bt_rootsplit; extern unsigned long bt_sortsplit, bt_split; BTREE *t; PAGE *h; pgno_t i, pcont, pinternal, pleaf; unsigned long ifree, lfree, nkeys; int levels; t = dbp->internal; pcont = pinternal = pleaf = 0; nkeys = ifree = lfree = 0; for (i = P_ROOT; (h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN)) != NULL; ++i) switch (h->flags & P_TYPE) { case P_BINTERNAL: case P_RINTERNAL: ++pinternal; ifree += h->upper - h->lower; break; case P_BLEAF: case P_RLEAF: ++pleaf; lfree += h->upper - h->lower; nkeys += NEXTINDEX(h); break; case P_OVERFLOW: ++pcont; break; } /* Count the levels of the tree. */ for (i = P_ROOT, levels = 0 ;; ++levels) { h = mpool_get(t->bt_mp, i, MPOOL_IGNOREPIN); if (h->flags & (P_BLEAF|P_RLEAF)) { if (levels == 0) levels = 1; break; } i = F_ISSET(t, R_RECNO) ? GETRINTERNAL(h, 0)->pgno : GETBINTERNAL(h, 0)->pgno; } (void)fprintf(stderr, "%d level%s with %lu keys", levels, levels == 1 ? "" : "s", nkeys); if (F_ISSET(t, R_RECNO)) (void)fprintf(stderr, " (%u header count)", t->bt_nrecs); (void)fprintf(stderr, "\n%u pages (leaf %u, internal %u, overflow %u)\n", pinternal + pleaf + pcont, pleaf, pinternal, pcont); (void)fprintf(stderr, "%lu cache hits, %lu cache misses\n", bt_cache_hit, bt_cache_miss); (void)fprintf(stderr, "%lu splits (%lu root splits, %lu sort splits)\n", bt_split, bt_rootsplit, bt_sortsplit); pleaf *= t->bt_psize - BTDATAOFF; if (pleaf) (void)fprintf(stderr, "%.0f%% leaf fill (%lu bytes used, %lu bytes free)\n", ((double)(pleaf - lfree) / pleaf) * 100, pleaf - lfree, lfree); pinternal *= t->bt_psize - BTDATAOFF; if (pinternal) (void)fprintf(stderr, "%.0f%% internal fill (%lu bytes used, %lu bytes free\n", ((double)(pinternal - ifree) / pinternal) * 100, pinternal - ifree, ifree); if (bt_pfxsaved) (void)fprintf(stderr, "prefix checking removed %lu bytes.\n", bt_pfxsaved); } #endif /* ifdef DEBUG */ ================================================ FILE: db/btree/bt_delete.c ================================================ /* $OpenBSD: bt_delete.c,v 1.11 2005/08/05 13:02:59 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include "btree.h" static int __bt_bdelete(BTREE *, const DBT *); static int __bt_curdel(BTREE *, const DBT *, PAGE *, unsigned int); static int __bt_pdelete(BTREE *, PAGE *); static int __bt_relink(BTREE *, PAGE *); static int __bt_stkacq(BTREE *, PAGE **, CURSOR *); /* * __bt_delete * Delete the item(s) referenced by a key. * * Return RET_SPECIAL if the key is not found. */ int __bt_delete(const DB *dbp, const DBT *key, unsigned int flags) { BTREE *t; CURSOR *c; PAGE *h; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Check for change to a read-only tree. */ if (F_ISSET(t, B_RDONLY)) { errno = EPERM; return (RET_ERROR); } switch (flags) { case 0: status = __bt_bdelete(t, key); break; case R_CURSOR: /* * If flags is R_CURSOR, delete the cursor. Must already * have started a scan and not have already deleted it. */ c = &t->bt_cursor; if (F_ISSET(c, CURS_INIT)) { if (F_ISSET(c, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE)) return (RET_SPECIAL); if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL) return (RET_ERROR); /* * If the page is about to be emptied, we'll need to * delete it, which means we have to acquire a stack. */ if (NEXTINDEX(h) == 1) if (__bt_stkacq(t, &h, &t->bt_cursor)) return (RET_ERROR); status = __bt_dleaf(t, NULL, h, c->pg.index); if (NEXTINDEX(h) == 0 && status == RET_SUCCESS) { if (__bt_pdelete(t, h)) return (RET_ERROR); } else mpool_put(t->bt_mp, h, status == RET_SUCCESS ? MPOOL_DIRTY : 0); break; } /* FALLTHROUGH */ default: errno = EINVAL; return (RET_ERROR); } if (status == RET_SUCCESS) F_SET(t, B_MODIFIED); return (status); } /* * __bt_stkacq -- * Acquire a stack so we can delete a cursor entry. * * Parameters: * t: tree * hp: pointer to current, pinned PAGE pointer * c: pointer to the cursor * * Returns: * 0 on success, 1 on failure */ static int __bt_stkacq(BTREE *t, PAGE **hp, CURSOR *c) { BINTERNAL *bi; EPG *e; EPGNO *parent; PAGE *h; indx_t idx; pgno_t pgno; recno_t nextpg, prevpg; int exact, level; /* * Find the first occurrence of the key in the tree. Toss the * currently locked page so we don't hit an already-locked page. */ h = *hp; mpool_put(t->bt_mp, h, 0); if ((e = __bt_search(t, &c->key, &exact)) == NULL) return (1); h = e->page; /* See if we got it in one shot. */ if (h->pgno == c->pg.pgno) goto ret; /* * Move right, looking for the page. At each move we have to move * up the stack until we don't have to move to the next page. If * we have to change pages at an internal level, we have to fix the * stack back up. */ while (h->pgno != c->pg.pgno) { if ((nextpg = h->nextpg) == P_INVALID) break; mpool_put(t->bt_mp, h, 0); /* Move up the stack. */ for (level = 0; (parent = BT_POP(t)) != NULL; ++level) { /* Get the parent page. */ if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) return (1); /* Move to the next index. */ if (parent->index != NEXTINDEX(h) - 1) { idx = parent->index + 1; BT_PUSH(t, h->pgno, idx); break; } mpool_put(t->bt_mp, h, 0); } /* Restore the stack. */ while (level--) { /* Push the next level down onto the stack. */ bi = GETBINTERNAL(h, idx); pgno = bi->pgno; BT_PUSH(t, pgno, 0); /* Lose the currently pinned page. */ mpool_put(t->bt_mp, h, 0); /* Get the next level down. */ if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL) return (1); idx = 0; } mpool_put(t->bt_mp, h, 0); if ((h = mpool_get(t->bt_mp, nextpg, 0)) == NULL) return (1); } if (h->pgno == c->pg.pgno) goto ret; /* Reacquire the original stack. */ mpool_put(t->bt_mp, h, 0); if ((e = __bt_search(t, &c->key, &exact)) == NULL) return (1); h = e->page; /* * Move left, looking for the page. At each move we have to move * up the stack until we don't have to change pages to move to the * next page. If we have to change pages at an internal level, we * have to fix the stack back up. */ while (h->pgno != c->pg.pgno) { if ((prevpg = h->prevpg) == P_INVALID) break; mpool_put(t->bt_mp, h, 0); /* Move up the stack. */ for (level = 0; (parent = BT_POP(t)) != NULL; ++level) { /* Get the parent page. */ if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) return (1); /* Move to the next index. */ if (parent->index != 0) { idx = parent->index - 1; BT_PUSH(t, h->pgno, idx); break; } mpool_put(t->bt_mp, h, 0); } /* Restore the stack. */ while (level--) { /* Push the next level down onto the stack. */ bi = GETBINTERNAL(h, idx); pgno = bi->pgno; /* Lose the currently pinned page. */ mpool_put(t->bt_mp, h, 0); /* Get the next level down. */ if ((h = mpool_get(t->bt_mp, pgno, 0)) == NULL) return (1); idx = NEXTINDEX(h) - 1; BT_PUSH(t, pgno, idx); } mpool_put(t->bt_mp, h, 0); if ((h = mpool_get(t->bt_mp, prevpg, 0)) == NULL) return (1); } ret: mpool_put(t->bt_mp, h, 0); return ((*hp = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL); } /* * __bt_bdelete -- * Delete all key/data pairs matching the specified key. * * Parameters: * t: tree * key: key to delete * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. */ static int __bt_bdelete(BTREE *t, const DBT *key) { EPG *e; PAGE *h; int deleted, exact, redo; deleted = 0; /* Find any matching record; __bt_search pins the page. */ loop: if ((e = __bt_search(t, key, &exact)) == NULL) return (deleted ? RET_SUCCESS : RET_ERROR); if (!exact) { mpool_put(t->bt_mp, e->page, 0); return (deleted ? RET_SUCCESS : RET_SPECIAL); } /* * Delete forward, then delete backward, from the found key. If * there are duplicates and we reach either side of the page, do * the key search again, so that we get them all. */ redo = 0; h = e->page; do { if (__bt_dleaf(t, key, h, e->index)) { mpool_put(t->bt_mp, h, 0); return (RET_ERROR); } if (F_ISSET(t, B_NODUPS)) { if (NEXTINDEX(h) == 0) { if (__bt_pdelete(t, h)) return (RET_ERROR); } else mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } deleted = 1; } while (e->index < NEXTINDEX(h) && __bt_cmp(t, key, e) == 0); /* Check for right-hand edge of the page. */ if (e->index == NEXTINDEX(h)) redo = 1; /* Delete from the key to the beginning of the page. */ while (e->index-- > 0) { if (__bt_cmp(t, key, e) != 0) break; if (__bt_dleaf(t, key, h, e->index) == RET_ERROR) { mpool_put(t->bt_mp, h, 0); return (RET_ERROR); } if (e->index == 0) redo = 1; } /* Check for an empty page. */ if (NEXTINDEX(h) == 0) { if (__bt_pdelete(t, h)) return (RET_ERROR); goto loop; } /* Put the page. */ mpool_put(t->bt_mp, h, MPOOL_DIRTY); if (redo) goto loop; return (RET_SUCCESS); } /* * __bt_pdelete -- * Delete a single page from the tree. * * Parameters: * t: tree * h: leaf page * * Returns: * RET_SUCCESS, RET_ERROR. * * Side-effects: * mpool_put's the page */ static int __bt_pdelete(BTREE *t, PAGE *h) { BINTERNAL *bi; PAGE *pg; EPGNO *parent; indx_t cnt, idx, *ip, offset; u_int32_t nksize; char *from; /* * Walk the parent page stack -- a LIFO stack of the pages that were * traversed when we searched for the page where the delete occurred. * Each stack entry is a page number and a page index offset. The * offset is for the page traversed on the search. We've just deleted * a page, so we have to delete the key from the parent page. * * If the delete from the parent page makes it empty, this process may * continue all the way up the tree. We stop if we reach the root page * (which is never deleted, it's just not worth the effort) or if the * delete does not empty the page. */ while ((parent = BT_POP(t)) != NULL) { /* Get the parent page. */ if ((pg = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) return (RET_ERROR); idx = parent->index; bi = GETBINTERNAL(pg, idx); /* Free any overflow pages. */ if (bi->flags & P_BIGKEY && __ovfl_delete(t, bi->bytes) == RET_ERROR) { mpool_put(t->bt_mp, pg, 0); return (RET_ERROR); } /* * Free the parent if it has only the one key and it's not the * root page. If it's the rootpage, turn it back into an empty * leaf page. */ if (NEXTINDEX(pg) == 1) { if (pg->pgno == P_ROOT) { pg->lower = BTDATAOFF; pg->upper = t->bt_psize; pg->flags = P_BLEAF; } else { if (__bt_relink(t, pg) || __bt_free(t, pg)) return (RET_ERROR); continue; } } else { /* Pack remaining key items at the end of the page. */ nksize = NBINTERNAL(bi->ksize); from = (char *)pg + pg->upper; memmove(from + nksize, from, (char *)bi - from); pg->upper += nksize; /* Adjust indices' offsets, shift the indices down. */ offset = pg->linp[idx]; for (cnt = idx, ip = &pg->linp[0]; cnt--; ++ip) if (ip[0] < offset) ip[0] += nksize; for (cnt = NEXTINDEX(pg) - idx; --cnt; ++ip) ip[0] = ip[1] < offset ? ip[1] + nksize : ip[1]; pg->lower -= sizeof(indx_t); } mpool_put(t->bt_mp, pg, MPOOL_DIRTY); break; } /* Free the leaf page, as long as it wasn't the root. */ if (h->pgno == P_ROOT) { mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } return (__bt_relink(t, h) || __bt_free(t, h)); } /* * __bt_dleaf -- * Delete a single record from a leaf page. * * Parameters: * t: tree * key: referenced key * h: page * idx: index on page to delete * * Returns: * RET_SUCCESS, RET_ERROR. */ int __bt_dleaf(BTREE *t, const DBT *key, PAGE *h, unsigned int idx) { BLEAF *bl; indx_t cnt, *ip, offset; u_int32_t nbytes; void *to; char *from; /* If this record is referenced by the cursor, delete the cursor. */ if (F_ISSET(&t->bt_cursor, CURS_INIT) && !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index == idx && __bt_curdel(t, key, h, idx)) return (RET_ERROR); /* If the entry uses overflow pages, make them available for reuse. */ to = bl = GETBLEAF(h, idx); if (bl->flags & P_BIGKEY && __ovfl_delete(t, bl->bytes) == RET_ERROR) return (RET_ERROR); if (bl->flags & P_BIGDATA && __ovfl_delete(t, bl->bytes + bl->ksize) == RET_ERROR) return (RET_ERROR); /* Pack the remaining key/data items at the end of the page. */ nbytes = NBLEAF(bl); from = (char *)h + h->upper; memmove(from + nbytes, from, (char *)to - from); h->upper += nbytes; /* Adjust the indices' offsets, shift the indices down. */ offset = h->linp[idx]; for (cnt = idx, ip = &h->linp[0]; cnt--; ++ip) if (ip[0] < offset) ip[0] += nbytes; for (cnt = NEXTINDEX(h) - idx; --cnt; ++ip) ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1]; h->lower -= sizeof(indx_t); /* If the cursor is on this page, adjust it as necessary. */ if (F_ISSET(&t->bt_cursor, CURS_INIT) && !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index > idx) --t->bt_cursor.pg.index; return (RET_SUCCESS); } /* * __bt_curdel -- * Delete the cursor. * * Parameters: * t: tree * key: referenced key (or NULL) * h: page * idx: index on page to delete * * Returns: * RET_SUCCESS, RET_ERROR. */ static int __bt_curdel(BTREE *t, const DBT *key, PAGE *h, unsigned int idx) { CURSOR *c; EPG e; PAGE *pg; int curcopy, status; /* * If there are duplicates, move forward or backward to one. * Otherwise, copy the key into the cursor area. */ c = &t->bt_cursor; F_CLR(c, CURS_AFTER | CURS_BEFORE | CURS_ACQUIRE); curcopy = 0; if (!F_ISSET(t, B_NODUPS)) { /* * We're going to have to do comparisons. If we weren't * provided a copy of the key, i.e. the user is deleting * the current cursor position, get one. */ if (key == NULL) { e.page = h; e.index = idx; if ((status = __bt_ret(t, &e, &c->key, &c->key, NULL, NULL, 1)) != RET_SUCCESS) return (status); curcopy = 1; key = &c->key; } /* Check previous key, if not at the beginning of the page. */ if (idx > 0) { e.page = h; e.index = idx - 1; if (__bt_cmp(t, key, &e) == 0) { F_SET(c, CURS_BEFORE); goto dup2; } } /* Check next key, if not at the end of the page. */ if (idx < NEXTINDEX(h) - 1) { e.page = h; e.index = idx + 1; if (__bt_cmp(t, key, &e) == 0) { F_SET(c, CURS_AFTER); goto dup2; } } /* Check previous key if at the beginning of the page. */ if (idx == 0 && h->prevpg != P_INVALID) { if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) return (RET_ERROR); e.page = pg; e.index = NEXTINDEX(pg) - 1; if (__bt_cmp(t, key, &e) == 0) { F_SET(c, CURS_BEFORE); goto dup1; } mpool_put(t->bt_mp, pg, 0); } /* Check next key if at the end of the page. */ if (idx == NEXTINDEX(h) - 1 && h->nextpg != P_INVALID) { if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) return (RET_ERROR); e.page = pg; e.index = 0; if (__bt_cmp(t, key, &e) == 0) { F_SET(c, CURS_AFTER); dup1: mpool_put(t->bt_mp, pg, 0); dup2: c->pg.pgno = e.page->pgno; c->pg.index = e.index; return (RET_SUCCESS); } mpool_put(t->bt_mp, pg, 0); } } e.page = h; e.index = idx; if (curcopy || (status = __bt_ret(t, &e, &c->key, &c->key, NULL, NULL, 1)) == RET_SUCCESS) { F_SET(c, CURS_ACQUIRE); return (RET_SUCCESS); } return (status); } /* * __bt_relink -- * Link around a deleted page. * * Parameters: * t: tree * h: page to be deleted */ static int __bt_relink(BTREE *t, PAGE *h) { PAGE *pg; if (h->nextpg != P_INVALID) { if ((pg = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) return (RET_ERROR); pg->prevpg = h->prevpg; mpool_put(t->bt_mp, pg, MPOOL_DIRTY); } if (h->prevpg != P_INVALID) { if ((pg = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) return (RET_ERROR); pg->nextpg = h->nextpg; mpool_put(t->bt_mp, pg, MPOOL_DIRTY); } return (0); } ================================================ FILE: db/btree/bt_get.c ================================================ /* $OpenBSD: bt_get.c,v 1.8 2005/08/05 13:02:59 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include "btree.h" /* * __BT_GET -- Get a record from the btree. * * Parameters: * dbp: pointer to access method * key: key to find * data: data to return * flag: currently unused * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. */ int __bt_get(const DB *dbp, const DBT *key, DBT *data, unsigned int flags) { BTREE *t; EPG *e; int exact, status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Get currently doesn't take any flags. */ if (flags) { errno = EINVAL; return (RET_ERROR); } if ((e = __bt_search(t, key, &exact)) == NULL) return (RET_ERROR); if (!exact) { mpool_put(t->bt_mp, e->page, 0); return (RET_SPECIAL); } status = __bt_ret(t, e, NULL, NULL, data, &t->bt_rdata, 0); /* * If the user is doing concurrent access, we copied the * key/data, toss the page. */ if (F_ISSET(t, B_DB_LOCK)) mpool_put(t->bt_mp, e->page, 0); else t->bt_pinned = e->page; return (status); } ================================================ FILE: db/btree/bt_open.c ================================================ /* $OpenBSD: bt_open.c,v 1.19 2015/12/28 22:08:18 mmcc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ /* * Implementation of btree access method for 4.4BSD. * * The design here was originally based on that of the btree access method * used in the Postgres database system at UC Berkeley. This implementation * is wholly independent of the Postgres code. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #ifndef EFTYPE # define EFTYPE ENOTSUP #endif /* ifndef EFTYPE */ #include #include #include "btree.h" #undef open #ifdef DEBUG # undef MINPSIZE # define MINPSIZE 128 #endif /* ifdef DEBUG */ #if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) # define BIG_ENDIAN 4321 # define LITTLE_ENDIAN 1234 #endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */ static int byteorder(void); static int nroot(BTREE *); static int tmp(void); /* * __BT_OPEN -- Open a btree. * * Creates and fills a DB struct, and calls the routine that actually * opens the btree. * * Parameters: * fname: filename (NULL for in-memory trees) * flags: open flag bits * mode: open permission bits * b: BTREEINFO pointer * * Returns: * NULL on failure, pointer to DB on success. * */ DB * __bt_open(const char *fname, int flags, int mode, const BTREEINFO *openinfo, int dflags) { struct stat sb; BTMETA m; BTREE *t; BTREEINFO b; DB *dbp; pgno_t ncache; ssize_t nr; int machine_lorder, saved_errno; t = NULL; /* * Intention is to make sure all of the user's selections are okay * here and then use them without checking. Can't be complete, since * we don't know the right page size, lorder or flags until the backing * file is opened. Also, the file's page size can cause the cachesize * to change. */ machine_lorder = byteorder(); if (openinfo) { b = *openinfo; /* Flags: R_DUP. */ if (b.flags & ~(R_DUP)) goto einval; /* * Page size must be indx_t aligned and >= MINPSIZE. Default * page size is set farther on, based on the underlying file * transfer size. */ if (b.psize && (b.psize < MINPSIZE || b.psize > MAX_PAGE_OFFSET + 1 || b.psize & (sizeof(indx_t) - 1))) goto einval; /* Minimum number of keys per page; absolute minimum is 2. */ if (b.minkeypage) { if (b.minkeypage < 2) goto einval; } else b.minkeypage = DEFMINKEYPAGE; /* If no comparison, use default comparison and prefix. */ if (b.compare == NULL) { b.compare = __bt_defcmp; if (b.prefix == NULL) b.prefix = __bt_defpfx; } if (b.lorder == 0) b.lorder = machine_lorder; } else { b.compare = __bt_defcmp; b.cachesize = 0; b.flags = 0; b.lorder = machine_lorder; b.minkeypage = DEFMINKEYPAGE; b.prefix = __bt_defpfx; b.psize = 0; } /* Check for the ubiquitous PDP-11. */ if (b.lorder != BIG_ENDIAN && b.lorder != LITTLE_ENDIAN) goto einval; /* Allocate and initialize DB and BTREE structures. */ if ((t = calloc(1, sizeof(BTREE))) == NULL) goto err; t->bt_fd = -1; /* Don't close unopened fd on error. */ t->bt_lorder = b.lorder; t->bt_order = NOT; t->bt_cmp = b.compare; t->bt_pfx = b.prefix; t->bt_rfd = -1; if ((t->bt_dbp = dbp = calloc(1, sizeof(DB))) == NULL) goto err; if (t->bt_lorder != machine_lorder) F_SET(t, B_NEEDSWAP); dbp->type = DB_BTREE; dbp->internal = t; dbp->close = __bt_close; dbp->del = __bt_delete; dbp->fd = __bt_fd; dbp->get = __bt_get; dbp->put = __bt_put; dbp->seq = __bt_seq; dbp->sync = __bt_sync; /* * If no file name was supplied, this is an in-memory btree and we * open a backing temporary file. Otherwise, it's a disk-based tree. */ if (fname) { switch (flags & O_ACCMODE) { case O_RDONLY: F_SET(t, B_RDONLY); break; case O_RDWR: break; case O_WRONLY: default: goto einval; } if ((t->bt_fd = open(fname, flags | O_CLOEXEC, mode)) < 0) goto err; } else { if ((flags & O_ACCMODE) != O_RDWR) goto einval; if ((t->bt_fd = tmp()) == -1) goto err; F_SET(t, B_INMEM); } if (fstat(t->bt_fd, &sb)) goto err; if (sb.st_size) { if ((nr = read(t->bt_fd, &m, sizeof(BTMETA))) < 0) goto err; if (nr != sizeof(BTMETA)) goto eftype; /* * Read in the meta-data. This can change the notion of what * the lorder, page size and flags are, and, when the page size * changes, the cachesize value can change too. If the user * specified the wrong byte order for an existing database, we * don't bother to return an error, we just clear the NEEDSWAP * bit. */ if (m.magic == BTREEMAGIC) F_CLR(t, B_NEEDSWAP); else { F_SET(t, B_NEEDSWAP); M_32_SWAP(m.magic); M_32_SWAP(m.version); M_32_SWAP(m.psize); M_32_SWAP(m.free); M_32_SWAP(m.nrecs); M_32_SWAP(m.flags); } if (m.magic != BTREEMAGIC || m.version != BTREEVERSION) goto eftype; if (m.psize < MINPSIZE || m.psize > MAX_PAGE_OFFSET + 1 || m.psize & (sizeof(indx_t) - 1)) goto eftype; if (m.flags & ~SAVEMETA) goto eftype; b.psize = m.psize; F_SET(t, m.flags); t->bt_free = m.free; t->bt_nrecs = m.nrecs; } else { /* * Set the page size to the best value for I/O to this file. * Don't overflow the page offset type. */ if (b.psize == 0) { b.psize = sb.st_blksize; if (b.psize < MINPSIZE) b.psize = MINPSIZE; if (b.psize > MAX_PAGE_OFFSET + 1) b.psize = MAX_PAGE_OFFSET + 1; } /* Set flag if duplicates permitted. */ if (!(b.flags & R_DUP)) F_SET(t, B_NODUPS); t->bt_free = P_INVALID; t->bt_nrecs = 0; F_SET(t, B_METADIRTY); } t->bt_psize = b.psize; /* Set the cache size; must be a multiple of the page size. */ if (b.cachesize && b.cachesize & (b.psize - 1)) b.cachesize += (~b.cachesize & (b.psize - 1)) + 1; if (b.cachesize < b.psize * MINCACHE) b.cachesize = b.psize * MINCACHE; /* Calculate number of pages to cache. */ ncache = (b.cachesize + t->bt_psize - 1) / t->bt_psize; /* * The btree data structure requires that at least two keys can fit on * a page, but other than that there's no fixed requirement. The user * specified a minimum number per page, and we translated that into the * number of bytes a key/data pair can use before being placed on an * overflow page. This calculation includes the page header, the size * of the index referencing the leaf item and the size of the leaf item * structure. Also, don't let the user specify a minkeypage such that * a key/data pair won't fit even if both key and data are on overflow * pages. */ t->bt_ovflsize = (t->bt_psize - BTDATAOFF) / b.minkeypage - (sizeof(indx_t) + NBLEAFDBT(0, 0)); if (t->bt_ovflsize < NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t)) t->bt_ovflsize = NBLEAFDBT(NOVFLSIZE, NOVFLSIZE) + sizeof(indx_t); /* Initialize the buffer pool. */ if ((t->bt_mp = mpool_open(NULL, t->bt_fd, t->bt_psize, ncache)) == NULL) goto err; if (!F_ISSET(t, B_INMEM)) mpool_filter(t->bt_mp, __bt_pgin, __bt_pgout, t); /* Create a root page if new tree. */ if (nroot(t) == RET_ERROR) goto err; /* Global flags. */ if (dflags & DB_LOCK) F_SET(t, B_DB_LOCK); if (dflags & DB_SHMEM) F_SET(t, B_DB_SHMEM); if (dflags & DB_TXN) F_SET(t, B_DB_TXN); return (dbp); einval: errno = EINVAL; goto err; eftype: errno = EFTYPE; goto err; err: saved_errno = errno; if (t) { free(t->bt_dbp); if (t->bt_fd != -1) (void)close(t->bt_fd); free(t); } errno = saved_errno; return (NULL); } /* * NROOT -- Create the root of a new tree. * * Parameters: * t: tree * * Returns: * RET_ERROR, RET_SUCCESS */ static int nroot(BTREE *t) { PAGE *meta, *root; pgno_t npg; if ((root = mpool_get(t->bt_mp, 1, 0)) != NULL) { if (root->lower == 0 && root->pgno == 0 && root->linp[0] == 0) { mpool_delete(t->bt_mp, root); errno = EINVAL; } else { mpool_put(t->bt_mp, root, 0); return (RET_SUCCESS); } } if (errno != EINVAL) /* It's OK to not exist. */ return (RET_ERROR); errno = 0; if ((meta = mpool_new(t->bt_mp, &npg, MPOOL_PAGE_NEXT)) == NULL) return (RET_ERROR); if ((root = mpool_new(t->bt_mp, &npg, MPOOL_PAGE_NEXT)) == NULL) return (RET_ERROR); if (npg != P_ROOT) return (RET_ERROR); root->pgno = npg; root->prevpg = root->nextpg = P_INVALID; root->lower = BTDATAOFF; root->upper = t->bt_psize; root->flags = P_BLEAF; memset(meta, 0, t->bt_psize); mpool_put(t->bt_mp, meta, MPOOL_DIRTY); mpool_put(t->bt_mp, root, MPOOL_DIRTY); return (RET_SUCCESS); } static int tmp(void) { sigset_t set, oset; int fd, len; char *envtmp = NULL; char path[PATH_MAX]; if (issetugid() == 0) envtmp = getenv("TMPDIR"); len = snprintf(path, sizeof(path), "%s/bt.XXXXXX", envtmp ? envtmp : "/tmp"); if (len < 0 || len >= sizeof(path)) { errno = ENAMETOOLONG; return(-1); } (void)sigfillset(&set); (void)sigprocmask(SIG_BLOCK, &set, &oset); #if defined(_AIX) || defined(__solaris__) || defined(__managarm__) if ((fd = mkstemp(path)) != -1) #else if ((fd = mkostemp(path, O_CLOEXEC)) != -1) #endif /* if defined(_AIX) || defined(__solaris__) */ (void)unlink(path); (void)sigprocmask(SIG_SETMASK, &oset, NULL); return(fd); } static int byteorder(void) { u_int32_t x; unsigned char *p; x = 0x01020304; p = (unsigned char *)&x; switch (*p) { case 1: return (BIG_ENDIAN); case 4: return (LITTLE_ENDIAN); default: return (0); } } int __bt_fd(const DB *dbp) { BTREE *t; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* In-memory database can't have a file descriptor. */ if (F_ISSET(t, B_INMEM)) { errno = ENOENT; return (-1); } return (t->bt_fd); } ================================================ FILE: db/btree/bt_overflow.c ================================================ /* $OpenBSD: bt_overflow.c,v 1.11 2015/01/16 16:48:51 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "btree.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) /* * Big key/data code. * * Big key and data entries are stored on linked lists of pages. The initial * reference is byte string stored with the key or data and is the page number * and size. The actual record is stored in a chain of pages linked by the * nextpg field of the PAGE header. * * The first page of the chain has a special property. If the record is used * by an internal page, it cannot be deleted and the P_PRESERVE bit will be set * in the header. * * XXX * A single DBT is written to each chain, so a lot of space on the last page * is wasted. This is a fairly major bug for some data sets. */ /* * __OVFL_GET -- Get an overflow key/data item. * * Parameters: * t: tree * p: pointer to { pgno_t, u_int32_t } * buf: storage address * bufsz: storage size * * Returns: * RET_ERROR, RET_SUCCESS */ int __ovfl_get(BTREE *t, void *p, size_t *ssz, void **buf, size_t *bufsz) { PAGE *h; pgno_t pg; size_t nb, plen; u_int32_t sz; void *tp; memmove(&pg, p, sizeof(pgno_t)); memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t)); *ssz = sz; #ifdef DEBUG if (pg == P_INVALID || sz == 0) abort(); #endif /* ifdef DEBUG */ /* Make the buffer bigger as necessary. */ if (*bufsz < sz) { tp = realloc(*buf, sz); if (tp == NULL) return (RET_ERROR); *buf = tp; *bufsz = sz; } /* * Step through the linked list of pages, copying the data on each one * into the buffer. Never copy more than the data's length. */ plen = t->bt_psize - BTDATAOFF; for (p = *buf;; p = (char *)p + nb, pg = h->nextpg) { if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); nb = MINIMUM(sz, plen); memmove(p, (char *)h + BTDATAOFF, nb); mpool_put(t->bt_mp, h, 0); if ((sz -= nb) == 0) break; } return (RET_SUCCESS); } /* * __OVFL_PUT -- Store an overflow key/data item. * * Parameters: * t: tree * data: DBT to store * pgno: storage page number * * Returns: * RET_ERROR, RET_SUCCESS */ int __ovfl_put(BTREE *t, const DBT *dbt, pgno_t *pg) { PAGE *h, *last; void *p; pgno_t npg; size_t nb, plen; u_int32_t sz; /* * Allocate pages and copy the key/data record into them. Store the * number of the first page in the chain. */ plen = t->bt_psize - BTDATAOFF; for (last = NULL, p = dbt->data, sz = dbt->size;; p = (char *)p + plen, last = h) { if ((h = __bt_new(t, &npg)) == NULL) return (RET_ERROR); h->pgno = npg; h->nextpg = h->prevpg = P_INVALID; h->flags = P_OVERFLOW; h->lower = h->upper = 0; nb = MINIMUM(sz, plen); memmove((char *)h + BTDATAOFF, p, nb); if (last) { last->nextpg = h->pgno; mpool_put(t->bt_mp, last, MPOOL_DIRTY); } else *pg = h->pgno; if ((sz -= nb) == 0) { mpool_put(t->bt_mp, h, MPOOL_DIRTY); break; } } return (RET_SUCCESS); } /* * __OVFL_DELETE -- Delete an overflow chain. * * Parameters: * t: tree * p: pointer to { pgno_t, u_int32_t } * * Returns: * RET_ERROR, RET_SUCCESS */ int __ovfl_delete(BTREE *t, void *p) { PAGE *h; pgno_t pg; size_t plen; u_int32_t sz; memmove(&pg, p, sizeof(pgno_t)); memmove(&sz, (char *)p + sizeof(pgno_t), sizeof(u_int32_t)); #ifdef DEBUG if (pg == P_INVALID || sz == 0) abort(); #endif /* ifdef DEBUG */ if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); /* Don't delete chains used by internal pages. */ if (h->flags & P_PRESERVE) { mpool_put(t->bt_mp, h, 0); return (RET_SUCCESS); } /* Step through the chain, calling the free routine for each page. */ for (plen = t->bt_psize - BTDATAOFF;; sz -= plen) { pg = h->nextpg; __bt_free(t, h); if (sz <= plen) break; if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); } return (RET_SUCCESS); } ================================================ FILE: db/btree/bt_page.c ================================================ /* $OpenBSD: bt_page.c,v 1.9 2005/08/05 13:02:59 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include "btree.h" /* * __bt_free -- * Put a page on the freelist. * * Parameters: * t: tree * h: page to free * * Returns: * RET_ERROR, RET_SUCCESS * * Side-effect: * mpool_put's the page. */ int __bt_free(BTREE *t, PAGE *h) { /* Insert the page at the head of the free list. */ h->prevpg = P_INVALID; h->nextpg = t->bt_free; t->bt_free = h->pgno; F_SET(t, B_METADIRTY); /* Make sure the page gets written back. */ return (mpool_put(t->bt_mp, h, MPOOL_DIRTY)); } /* * __bt_new -- * Get a new page, preferably from the freelist. * * Parameters: * t: tree * npg: storage for page number. * * Returns: * Pointer to a page, NULL on error. */ PAGE * __bt_new(BTREE *t, pgno_t *npg) { PAGE *h; if (t->bt_free != P_INVALID && (h = mpool_get(t->bt_mp, t->bt_free, 0)) != NULL) { *npg = t->bt_free; t->bt_free = h->nextpg; F_SET(t, B_METADIRTY); return (h); } return (mpool_new(t->bt_mp, npg, MPOOL_PAGE_NEXT)); } ================================================ FILE: db/btree/bt_put.c ================================================ /* $OpenBSD: bt_put.c,v 1.13 2005/08/05 13:02:59 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "btree.h" static EPG *bt_fast(BTREE *, const DBT *, const DBT *, int *); /* * __BT_PUT -- Add a btree item to the tree. * * Parameters: * dbp: pointer to access method * key: key * data: data * flag: R_NOOVERWRITE * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is already in the * tree and R_NOOVERWRITE specified. */ int __bt_put(const DB *dbp, DBT *key, const DBT *data, unsigned int flags) { BTREE *t; DBT tkey, tdata; EPG *e; PAGE *h; indx_t idx, nxtindex; pgno_t pg; u_int32_t nbytes, size32; int dflags, exact, status; char *dest, db[NOVFLSIZE], kb[NOVFLSIZE]; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Check for change to a read-only tree. */ if (F_ISSET(t, B_RDONLY)) { errno = EPERM; return (RET_ERROR); } switch (flags) { case 0: case R_NOOVERWRITE: break; case R_CURSOR: /* * If flags is R_CURSOR, put the cursor. Must already * have started a scan and not have already deleted it. */ if (F_ISSET(&t->bt_cursor, CURS_INIT) && !F_ISSET(&t->bt_cursor, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE)) break; /* FALLTHROUGH */ default: errno = EINVAL; return (RET_ERROR); } /* * If the key/data pair won't fit on a page, store it on overflow * pages. Only put the key on the overflow page if the pair are * still too big after moving the data to an overflow page. * * XXX * If the insert fails later on, the overflow pages aren't recovered. */ dflags = 0; if (key->size + data->size > t->bt_ovflsize) { if (key->size > t->bt_ovflsize) { storekey: if (__ovfl_put(t, key, &pg) == RET_ERROR) return (RET_ERROR); tkey.data = kb; tkey.size = NOVFLSIZE; memmove(kb, &pg, sizeof(pgno_t)); size32 = key->size; memmove(kb + sizeof(pgno_t), &size32, sizeof(u_int32_t)); dflags |= P_BIGKEY; key = &tkey; } if (key->size + data->size > t->bt_ovflsize) { if (__ovfl_put(t, data, &pg) == RET_ERROR) return (RET_ERROR); tdata.data = db; tdata.size = NOVFLSIZE; memmove(db, &pg, sizeof(pgno_t)); size32 = data->size; memmove(db + sizeof(pgno_t), &size32, sizeof(u_int32_t)); dflags |= P_BIGDATA; data = &tdata; } if (key->size + data->size > t->bt_ovflsize) goto storekey; } /* Replace the cursor. */ if (flags == R_CURSOR) { if ((h = mpool_get(t->bt_mp, t->bt_cursor.pg.pgno, 0)) == NULL) return (RET_ERROR); idx = t->bt_cursor.pg.index; goto delete; } /* * Find the key to delete, or, the location at which to insert. * Bt_fast and __bt_search both pin the returned page. */ if (t->bt_order == NOT || (e = bt_fast(t, key, data, &exact)) == NULL) if ((e = __bt_search(t, key, &exact)) == NULL) return (RET_ERROR); h = e->page; idx = e->index; /* * Add the key/data pair to the tree. If an identical key is already * in the tree, and R_NOOVERWRITE is set, an error is returned. If * R_NOOVERWRITE is not set, the key is either added (if duplicates are * permitted) or an error is returned. */ switch (flags) { case R_NOOVERWRITE: if (!exact) break; mpool_put(t->bt_mp, h, 0); return (RET_SPECIAL); default: if (!exact || !F_ISSET(t, B_NODUPS)) break; /* * !!! * Note, the delete may empty the page, so we need to put a * new entry into the page immediately. */ delete: if (__bt_dleaf(t, key, h, idx) == RET_ERROR) { mpool_put(t->bt_mp, h, 0); return (RET_ERROR); } break; } /* * If not enough room, or the user has put a ceiling on the number of * keys permitted in the page, split the page. The split code will * insert the key and data and unpin the current page. If inserting * into the offset array, shift the pointers up. */ nbytes = NBLEAFDBT(key->size, data->size); if (h->upper - h->lower < nbytes + sizeof(indx_t)) { if ((status = __bt_split(t, h, key, data, dflags, nbytes, idx)) != RET_SUCCESS) return (status); goto success; } if (idx < (nxtindex = NEXTINDEX(h))) memmove(h->linp + idx + 1, h->linp + idx, (nxtindex - idx) * sizeof(indx_t)); h->lower += sizeof(indx_t); h->linp[idx] = h->upper -= nbytes; dest = (char *)h + h->upper; WR_BLEAF(dest, key, data, dflags); /* If the cursor is on this page, adjust it as necessary. */ if (F_ISSET(&t->bt_cursor, CURS_INIT) && !F_ISSET(&t->bt_cursor, CURS_ACQUIRE) && t->bt_cursor.pg.pgno == h->pgno && t->bt_cursor.pg.index >= idx) ++t->bt_cursor.pg.index; if (t->bt_order == NOT) { if (h->nextpg == P_INVALID) { if (idx == NEXTINDEX(h) - 1) { t->bt_order = FORWARD; t->bt_last.index = idx; t->bt_last.pgno = h->pgno; } } else if (h->prevpg == P_INVALID) { if (idx == 0) { t->bt_order = BACK; t->bt_last.index = 0; t->bt_last.pgno = h->pgno; } } } mpool_put(t->bt_mp, h, MPOOL_DIRTY); success: if (flags == R_SETCURSOR) __bt_setcur(t, e->page->pgno, e->index); F_SET(t, B_MODIFIED); return (RET_SUCCESS); } #ifdef STATISTICS unsigned long bt_cache_hit, bt_cache_miss; #endif /* ifdef STATISTICS */ /* * BT_FAST -- Do a quick check for sorted data. * * Parameters: * t: tree * key: key to insert * * Returns: * EPG for new record or NULL if not found. */ static EPG * bt_fast(BTREE *t, const DBT *key, const DBT *data, int *exactp) { PAGE *h; u_int32_t nbytes; int cmp; if ((h = mpool_get(t->bt_mp, t->bt_last.pgno, 0)) == NULL) { t->bt_order = NOT; return (NULL); } t->bt_cur.page = h; t->bt_cur.index = t->bt_last.index; /* * If won't fit in this page or have too many keys in this page, * have to search to get split stack. */ nbytes = NBLEAFDBT(key->size, data->size); if (h->upper - h->lower < nbytes + sizeof(indx_t)) goto miss; if (t->bt_order == FORWARD) { if (t->bt_cur.page->nextpg != P_INVALID) goto miss; if (t->bt_cur.index != NEXTINDEX(h) - 1) goto miss; if ((cmp = __bt_cmp(t, key, &t->bt_cur)) < 0) goto miss; t->bt_last.index = cmp ? ++t->bt_cur.index : t->bt_cur.index; } else { if (t->bt_cur.page->prevpg != P_INVALID) goto miss; if (t->bt_cur.index != 0) goto miss; if ((cmp = __bt_cmp(t, key, &t->bt_cur)) > 0) goto miss; t->bt_last.index = 0; } *exactp = cmp == 0; #ifdef STATISTICS ++bt_cache_hit; #endif /* ifdef STATISTICS */ return (&t->bt_cur); miss: #ifdef STATISTICS ++bt_cache_miss; #endif /* ifdef STATISTICS */ t->bt_order = NOT; mpool_put(t->bt_mp, h, 0); return (NULL); } ================================================ FILE: db/btree/bt_search.c ================================================ /* $OpenBSD: bt_search.c,v 1.10 2005/08/05 13:02:59 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include "btree.h" static int __bt_snext(BTREE *, PAGE *, const DBT *, int *); static int __bt_sprev(BTREE *, PAGE *, const DBT *, int *); /* * __bt_search -- * Search a btree for a key. * * Parameters: * t: tree to search * key: key to find * exactp: pointer to exact match flag * * Returns: * The EPG for matching record, if any, or the EPG for the location * of the key, if it were inserted into the tree, is entered into * the bt_cur field of the tree. A pointer to the field is returned. */ EPG * __bt_search(BTREE *t, const DBT *key, int *exactp) { PAGE *h; indx_t base, idx, lim; pgno_t pg; int cmp; BT_CLR(t); for (pg = P_ROOT;;) { if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (NULL); /* Do a binary search on the current page. */ t->bt_cur.page = h; for (base = 0, lim = NEXTINDEX(h); lim; lim >>= 1) { t->bt_cur.index = idx = base + (lim >> 1); if ((cmp = __bt_cmp(t, key, &t->bt_cur)) == 0) { if (h->flags & P_BLEAF) { *exactp = 1; return (&t->bt_cur); } goto next; } if (cmp > 0) { base = idx + 1; --lim; } } /* * If it's a leaf page, we're almost done. If no duplicates * are allowed, or we have an exact match, we're done. Else, * it's possible that there were matching keys on this page, * which later deleted, and we're on a page with no matches * while there are matches on other pages. If at the start or * end of a page, check the adjacent page. */ if (h->flags & P_BLEAF) { if (!F_ISSET(t, B_NODUPS)) { if (base == 0 && h->prevpg != P_INVALID && __bt_sprev(t, h, key, exactp)) return (&t->bt_cur); if (base == NEXTINDEX(h) && h->nextpg != P_INVALID && __bt_snext(t, h, key, exactp)) return (&t->bt_cur); } *exactp = 0; t->bt_cur.index = base; return (&t->bt_cur); } /* * No match found. Base is the smallest index greater than * key and may be zero or a last + 1 index. If it's non-zero, * decrement by one, and record the internal page which should * be a parent page for the key. If a split later occurs, the * inserted page will be to the right of the saved page. */ idx = base ? base - 1 : base; next: BT_PUSH(t, h->pgno, idx); pg = GETBINTERNAL(h, idx)->pgno; mpool_put(t->bt_mp, h, 0); } } /* * __bt_snext -- * Check for an exact match after the key. * * Parameters: * t: tree * h: current page * key: key * exactp: pointer to exact match flag * * Returns: * If an exact match found. */ static int __bt_snext(BTREE *t, PAGE *h, const DBT *key, int *exactp) { EPG e; /* * Get the next page. The key is either an exact * match, or not as good as the one we already have. */ if ((e.page = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) return (0); e.index = 0; if (__bt_cmp(t, key, &e) == 0) { mpool_put(t->bt_mp, h, 0); t->bt_cur = e; *exactp = 1; return (1); } mpool_put(t->bt_mp, e.page, 0); return (0); } /* * __bt_sprev -- * Check for an exact match before the key. * * Parameters: * t: tree * h: current page * key: key * exactp: pointer to exact match flag * * Returns: * If an exact match found. */ static int __bt_sprev(BTREE *t, PAGE *h, const DBT *key, int *exactp) { EPG e; /* * Get the previous page. The key is either an exact * match, or not as good as the one we already have. */ if ((e.page = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) return (0); e.index = NEXTINDEX(e.page) - 1; if (__bt_cmp(t, key, &e) == 0) { mpool_put(t->bt_mp, h, 0); t->bt_cur = e; *exactp = 1; return (1); } mpool_put(t->bt_mp, e.page, 0); return (0); } ================================================ FILE: db/btree/bt_seq.c ================================================ /* $OpenBSD: bt_seq.c,v 1.12 2022/12/27 17:10:06 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "btree.h" static int __bt_first(BTREE *, const DBT *, EPG *, int *); static int __bt_seqadv(BTREE *, EPG *, int); static int __bt_seqset(BTREE *, EPG *, DBT *, int); /* * Sequential scan support. * * The tree can be scanned sequentially, starting from either end of the * tree or from any specific key. A scan request before any scanning is * done is initialized as starting from the least node. */ /* * __bt_seq -- * Btree sequential scan interface. * * Parameters: * dbp: pointer to access method * key: key for positioning and return value * data: data return value * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV. * * Returns: * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. */ int __bt_seq(const DB *dbp, DBT *key, DBT *data, unsigned int flags) { BTREE *t; EPG e; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* * If scan uninitialized as yet, or starting at a specific record, set * the scan to a specific key. Both __bt_seqset and __bt_seqadv pin * the page the cursor references if they're successful. */ switch (flags) { case R_NEXT: case R_PREV: if (F_ISSET(&t->bt_cursor, CURS_INIT)) { status = __bt_seqadv(t, &e, flags); break; } /* FALLTHROUGH */ case R_FIRST: case R_LAST: case R_CURSOR: status = __bt_seqset(t, &e, key, flags); break; default: errno = EINVAL; return (RET_ERROR); } if (status == RET_SUCCESS) { __bt_setcur(t, e.page->pgno, e.index); status = __bt_ret(t, &e, key, &t->bt_rkey, data, &t->bt_rdata, 0); /* * If the user is doing concurrent access, we copied the * key/data, toss the page. */ if (F_ISSET(t, B_DB_LOCK)) mpool_put(t->bt_mp, e.page, 0); else t->bt_pinned = e.page; } return (status); } /* * __bt_seqset -- * Set the sequential scan to a specific key. * * Parameters: * t: tree * ep: storage for returned key * key: key for initial scan position * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV * * Side effects: * Pins the page the cursor references. * * Returns: * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. */ static int __bt_seqset(BTREE *t, EPG *ep, DBT *key, int flags) { PAGE *h; pgno_t pg; int exact; /* * Find the first, last or specific key in the tree and point the * cursor at it. The cursor may not be moved until a new key has * been found. */ switch (flags) { case R_CURSOR: /* Keyed scan. */ /* * Find the first instance of the key or the smallest key * which is greater than or equal to the specified key. */ if (key->data == NULL || key->size == 0) { errno = EINVAL; return (RET_ERROR); } return (__bt_first(t, key, ep, &exact)); case R_FIRST: /* First record. */ case R_NEXT: /* Walk down the left-hand side of the tree. */ for (pg = P_ROOT;;) { if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); /* Check for an empty tree. */ if (NEXTINDEX(h) == 0) { mpool_put(t->bt_mp, h, 0); return (RET_SPECIAL); } if (h->flags & (P_BLEAF | P_RLEAF)) break; pg = GETBINTERNAL(h, 0)->pgno; mpool_put(t->bt_mp, h, 0); } ep->page = h; ep->index = 0; break; case R_LAST: /* Last record. */ case R_PREV: /* Walk down the right-hand side of the tree. */ for (pg = P_ROOT;;) { if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); /* Check for an empty tree. */ if (NEXTINDEX(h) == 0) { mpool_put(t->bt_mp, h, 0); return (RET_SPECIAL); } if (h->flags & (P_BLEAF | P_RLEAF)) break; pg = GETBINTERNAL(h, NEXTINDEX(h) - 1)->pgno; mpool_put(t->bt_mp, h, 0); } ep->page = h; ep->index = NEXTINDEX(h) - 1; break; } return (RET_SUCCESS); } /* * __bt_seqadvance -- * Advance the sequential scan. * * Parameters: * t: tree * flags: R_NEXT, R_PREV * * Side effects: * Pins the page the new key/data record is on. * * Returns: * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. */ static int __bt_seqadv(BTREE *t, EPG *ep, int flags) { CURSOR *c; PAGE *h; indx_t idx; pgno_t pg; int exact; /* * There are a couple of states that we can be in. The cursor has * been initialized by the time we get here, but that's all we know. */ c = &t->bt_cursor; /* * The cursor was deleted where there weren't any duplicate records, * so the key was saved. Find out where that key would go in the * current tree. It doesn't matter if the returned key is an exact * match or not -- if it's an exact match, the record was added after * the delete so we can just return it. If not, as long as there's * a record there, return it. */ if (F_ISSET(c, CURS_ACQUIRE)) return (__bt_first(t, &c->key, ep, &exact)); /* Get the page referenced by the cursor. */ if ((h = mpool_get(t->bt_mp, c->pg.pgno, 0)) == NULL) return (RET_ERROR); /* * Find the next/previous record in the tree and point the cursor at * it. The cursor may not be moved until a new key has been found. */ switch (flags) { case R_NEXT: /* Next record. */ /* * The cursor was deleted in duplicate records, and moved * forward to a record that has yet to be returned. Clear * that flag, and return the record. */ if (F_ISSET(c, CURS_AFTER)) goto usecurrent; idx = c->pg.index; if (++idx == NEXTINDEX(h)) { pg = h->nextpg; mpool_put(t->bt_mp, h, 0); if (pg == P_INVALID) return (RET_SPECIAL); if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); idx = 0; } break; case R_PREV: /* Previous record. */ /* * The cursor was deleted in duplicate records, and moved * backward to a record that has yet to be returned. Clear * that flag, and return the record. */ if (F_ISSET(c, CURS_BEFORE)) { usecurrent: F_CLR(c, CURS_AFTER | CURS_BEFORE); ep->page = h; ep->index = c->pg.index; return (RET_SUCCESS); } idx = c->pg.index; if (idx == 0) { pg = h->prevpg; mpool_put(t->bt_mp, h, 0); if (pg == P_INVALID) return (RET_SPECIAL); if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); idx = NEXTINDEX(h) - 1; } else --idx; break; } ep->page = h; ep->index = idx; return (RET_SUCCESS); } /* * __bt_first -- * Find the first entry. * * Parameters: * t: the tree * key: the key * erval: return EPG * exactp: pointer to exact match flag * * Returns: * The first entry in the tree greater than or equal to key, * or RET_SPECIAL if no such key exists. */ static int __bt_first(BTREE *t, const DBT *key, EPG *erval, int *exactp) { PAGE *h; EPG *ep, save; pgno_t pg; /* * Find any matching record; __bt_search pins the page. * * If it's an exact match and duplicates are possible, walk backwards * in the tree until we find the first one. Otherwise, make sure it's * a valid key (__bt_search may return an index just past the end of a * page) and return it. */ if ((ep = __bt_search(t, key, exactp)) == NULL) return (0); if (*exactp) { if (F_ISSET(t, B_NODUPS)) { *erval = *ep; return (RET_SUCCESS); } /* * Walk backwards, as long as the entry matches and there are * keys left in the tree. Save a copy of each match in case * we go too far. */ save = *ep; h = ep->page; do { if (save.page->pgno != ep->page->pgno) { mpool_put(t->bt_mp, save.page, 0); save = *ep; } else save.index = ep->index; /* * Don't unpin the page the last (or original) match * was on, but make sure it's unpinned if an error * occurs. */ if (ep->index == 0) { if (h->prevpg == P_INVALID) break; if (h->pgno != save.page->pgno) mpool_put(t->bt_mp, h, 0); if ((h = mpool_get(t->bt_mp, h->prevpg, 0)) == NULL) { if (h->pgno == save.page->pgno) mpool_put(t->bt_mp, save.page, 0); return (RET_ERROR); } ep->page = h; ep->index = NEXTINDEX(h); } --ep->index; } while (__bt_cmp(t, key, ep) == 0); /* * Reach here with the last page that was looked at pinned, * which may or may not be the same as the last (or original) * match page. If it's not useful, release it. */ if (h->pgno != save.page->pgno) mpool_put(t->bt_mp, h, 0); *erval = save; return (RET_SUCCESS); } /* If at the end of a page, find the next entry. */ if (ep->index == NEXTINDEX(ep->page)) { h = ep->page; pg = h->nextpg; mpool_put(t->bt_mp, h, 0); if (pg == P_INVALID) return (RET_SPECIAL); if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); ep->index = 0; ep->page = h; } *erval = *ep; return (RET_SUCCESS); } /* * __bt_setcur -- * Set the cursor to an entry in the tree. * * Parameters: * t: the tree * pgno: page number * idx: page index */ void __bt_setcur(BTREE *t, pgno_t pgno, unsigned int idx) { /* Lose any already deleted key. */ if (t->bt_cursor.key.data != NULL) { free(t->bt_cursor.key.data); t->bt_cursor.key.size = 0; t->bt_cursor.key.data = NULL; } F_CLR(&t->bt_cursor, CURS_ACQUIRE | CURS_AFTER | CURS_BEFORE); /* Update the cursor. */ t->bt_cursor.pg.pgno = pgno; t->bt_cursor.pg.index = idx; F_SET(&t->bt_cursor, CURS_INIT); } ================================================ FILE: db/btree/bt_split.c ================================================ /* $OpenBSD: bt_split.c,v 1.13 2005/08/05 13:03:00 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "btree.h" static int bt_broot(BTREE *, PAGE *, PAGE *, PAGE *); static PAGE *bt_page(BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t); static int bt_preserve(BTREE *, pgno_t); static PAGE *bt_psplit(BTREE *, PAGE *, PAGE *, PAGE *, indx_t *, size_t); static PAGE *bt_root(BTREE *, PAGE *, PAGE **, PAGE **, indx_t *, size_t); static int bt_rroot(BTREE *, PAGE *, PAGE *, PAGE *); static recno_t rec_total(PAGE *); #ifdef STATISTICS unsigned long bt_rootsplit, bt_split, bt_sortsplit, bt_pfxsaved; #endif /* ifdef STATISTICS */ /* * __BT_SPLIT -- Split the tree. * * Parameters: * t: tree * sp: page to split * key: key to insert * data: data to insert * flags: BIGKEY/BIGDATA flags * ilen: insert length * skip: index to leave open * * Returns: * RET_ERROR, RET_SUCCESS */ int __bt_split(BTREE *t, PAGE *sp, const DBT *key, const DBT *data, int flags, size_t ilen, u_int32_t argskip) { BINTERNAL *bi; BLEAF *bl, *tbl; DBT a, b; EPGNO *parent; PAGE *h, *l, *r, *lchild, *rchild; indx_t nxtindex; u_int16_t skip; u_int32_t n, nbytes, nksize; int parentsplit; char *dest; /* * Split the page into two pages, l and r. The split routines return * a pointer to the page into which the key should be inserted and with * skip set to the offset which should be used. Additionally, l and r * are pinned. */ skip = argskip; h = sp->pgno == P_ROOT ? bt_root(t, sp, &l, &r, &skip, ilen) : bt_page(t, sp, &l, &r, &skip, ilen); if (h == NULL) return (RET_ERROR); /* * Insert the new key/data pair into the leaf page. (Key inserts * always cause a leaf page to split first.) */ h->linp[skip] = h->upper -= ilen; dest = (char *)h + h->upper; if (F_ISSET(t, R_RECNO)) WR_RLEAF(dest, data, flags) else WR_BLEAF(dest, key, data, flags) /* If the root page was split, make it look right. */ if (sp->pgno == P_ROOT && (F_ISSET(t, R_RECNO) ? bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR) goto err2; /* * Now we walk the parent page stack -- a LIFO stack of the pages that * were traversed when we searched for the page that split. Each stack * entry is a page number and a page index offset. The offset is for * the page traversed on the search. We've just split a page, so we * have to insert a new key into the parent page. * * If the insert into the parent page causes it to split, may have to * continue splitting all the way up the tree. We stop if the root * splits or the page inserted into didn't have to split to hold the * new key. Some algorithms replace the key for the old page as well * as the new page. We don't, as there's no reason to believe that the * first key on the old page is any better than the key we have, and, * in the case of a key being placed at index 0 causing the split, the * key is unavailable. * * There are a maximum of 5 pages pinned at any time. We keep the left * and right pages pinned while working on the parent. The 5 are the * two children, left parent and right parent (when the parent splits) * and the root page or the overflow key page when calling bt_preserve. * This code must make sure that all pins are released other than the * root page or overflow page which is unlocked elsewhere. */ while ((parent = BT_POP(t)) != NULL) { lchild = l; rchild = r; /* Get the parent page. */ if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) goto err2; /* * The new key goes ONE AFTER the index, because the split * was to the right. */ skip = parent->index + 1; /* * Calculate the space needed on the parent page. * * Prefix trees: space hack when inserting into BINTERNAL * pages. Retain only what's needed to distinguish between * the new entry and the LAST entry on the page to its left. * If the keys compare equal, retain the entire key. Note, * we don't touch overflow keys, and the entire key must be * retained for the next-to-left most key on the leftmost * page of each level, or the search will fail. Applicable * ONLY to internal pages that have leaf pages as children. * Further reduction of the key between pairs of internal * pages loses too much information. */ switch (rchild->flags & P_TYPE) { case P_BINTERNAL: bi = GETBINTERNAL(rchild, 0); nbytes = NBINTERNAL(bi->ksize); break; case P_BLEAF: bl = GETBLEAF(rchild, 0); nbytes = NBINTERNAL(bl->ksize); if (t->bt_pfx && !(bl->flags & P_BIGKEY) && (h->prevpg != P_INVALID || skip > 1)) { tbl = GETBLEAF(lchild, NEXTINDEX(lchild) - 1); a.size = tbl->ksize; a.data = tbl->bytes; b.size = bl->ksize; b.data = bl->bytes; nksize = t->bt_pfx(&a, &b); n = NBINTERNAL(nksize); if (n < nbytes) { #ifdef STATISTICS bt_pfxsaved += nbytes - n; #endif /* ifdef STATISTICS */ nbytes = n; } else nksize = 0; } else nksize = 0; break; case P_RINTERNAL: case P_RLEAF: nbytes = NRINTERNAL; break; default: abort(); } /* Split the parent page if necessary or shift the indices. */ if (h->upper - h->lower < nbytes + sizeof(indx_t)) { sp = h; h = h->pgno == P_ROOT ? bt_root(t, h, &l, &r, &skip, nbytes) : bt_page(t, h, &l, &r, &skip, nbytes); if (h == NULL) goto err1; parentsplit = 1; } else { if (skip < (nxtindex = NEXTINDEX(h))) memmove(h->linp + skip + 1, h->linp + skip, (nxtindex - skip) * sizeof(indx_t)); h->lower += sizeof(indx_t); parentsplit = 0; } /* Insert the key into the parent page. */ switch (rchild->flags & P_TYPE) { case P_BINTERNAL: h->linp[skip] = h->upper -= nbytes; dest = (char *)h + h->linp[skip]; memmove(dest, bi, nbytes); ((BINTERNAL *)dest)->pgno = rchild->pgno; break; case P_BLEAF: h->linp[skip] = h->upper -= nbytes; dest = (char *)h + h->linp[skip]; WR_BINTERNAL(dest, nksize ? nksize : bl->ksize, rchild->pgno, bl->flags & P_BIGKEY); memmove(dest, bl->bytes, nksize ? nksize : bl->ksize); if (bl->flags & P_BIGKEY && bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR) goto err1; break; case P_RINTERNAL: /* * Update the left page count. If split * added at index 0, fix the correct page. */ if (skip > 0) dest = (char *)h + h->linp[skip - 1]; else dest = (char *)l + l->linp[NEXTINDEX(l) - 1]; ((RINTERNAL *)dest)->nrecs = rec_total(lchild); ((RINTERNAL *)dest)->pgno = lchild->pgno; /* Update the right page count. */ h->linp[skip] = h->upper -= nbytes; dest = (char *)h + h->linp[skip]; ((RINTERNAL *)dest)->nrecs = rec_total(rchild); ((RINTERNAL *)dest)->pgno = rchild->pgno; break; case P_RLEAF: /* * Update the left page count. If split * added at index 0, fix the correct page. */ if (skip > 0) dest = (char *)h + h->linp[skip - 1]; else dest = (char *)l + l->linp[NEXTINDEX(l) - 1]; ((RINTERNAL *)dest)->nrecs = NEXTINDEX(lchild); ((RINTERNAL *)dest)->pgno = lchild->pgno; /* Update the right page count. */ h->linp[skip] = h->upper -= nbytes; dest = (char *)h + h->linp[skip]; ((RINTERNAL *)dest)->nrecs = NEXTINDEX(rchild); ((RINTERNAL *)dest)->pgno = rchild->pgno; break; default: abort(); } /* Unpin the held pages. */ if (!parentsplit) { mpool_put(t->bt_mp, h, MPOOL_DIRTY); break; } /* If the root page was split, make it look right. */ if (sp->pgno == P_ROOT && (F_ISSET(t, R_RECNO) ? bt_rroot(t, sp, l, r) : bt_broot(t, sp, l, r)) == RET_ERROR) goto err1; mpool_put(t->bt_mp, lchild, MPOOL_DIRTY); mpool_put(t->bt_mp, rchild, MPOOL_DIRTY); } /* Unpin the held pages. */ mpool_put(t->bt_mp, l, MPOOL_DIRTY); mpool_put(t->bt_mp, r, MPOOL_DIRTY); /* Clear any pages left on the stack. */ return (RET_SUCCESS); /* * If something fails in the above loop we were already walking back * up the tree and the tree is now inconsistent. Nothing much we can * do about it but release any memory we're holding. */ err1: mpool_put(t->bt_mp, lchild, MPOOL_DIRTY); mpool_put(t->bt_mp, rchild, MPOOL_DIRTY); err2: mpool_put(t->bt_mp, l, 0); mpool_put(t->bt_mp, r, 0); __dbpanic(t->bt_dbp); return (RET_ERROR); } /* * BT_PAGE -- Split a non-root page of a btree. * * Parameters: * t: tree * h: root page * lp: pointer to left page pointer * rp: pointer to right page pointer * skip: pointer to index to leave open * ilen: insert length * * Returns: * Pointer to page in which to insert or NULL on error. */ static PAGE * bt_page(BTREE *t, PAGE *h, PAGE **lp, PAGE **rp, indx_t *skip, size_t ilen) { PAGE *l, *r, *tp; pgno_t npg; #ifdef STATISTICS ++bt_split; #endif /* ifdef STATISTICS */ /* Put the new right page for the split into place. */ if ((r = __bt_new(t, &npg)) == NULL) return (NULL); r->pgno = npg; r->lower = BTDATAOFF; r->upper = t->bt_psize; r->nextpg = h->nextpg; r->prevpg = h->pgno; r->flags = h->flags & P_TYPE; /* * If we're splitting the last page on a level because we're appending * a key to it (skip is NEXTINDEX()), it's likely that the data is * sorted. Adding an empty page on the side of the level is less work * and can push the fill factor much higher than normal. If we're * wrong it's no big deal, we'll just do the split the right way next * time. It may look like it's equally easy to do a similar hack for * reverse sorted data, that is, split the tree left, but it's not. * Don't even try. */ if (h->nextpg == P_INVALID && *skip == NEXTINDEX(h)) { #ifdef STATISTICS ++bt_sortsplit; #endif /* ifdef STATISTICS */ h->nextpg = r->pgno; r->lower = BTDATAOFF + sizeof(indx_t); *skip = 0; *lp = h; *rp = r; return (r); } /* Put the new left page for the split into place. */ if ((l = (PAGE *)malloc(t->bt_psize)) == NULL) { mpool_put(t->bt_mp, r, 0); return (NULL); } memset(l, 0xff, t->bt_psize); l->pgno = h->pgno; l->nextpg = r->pgno; l->prevpg = h->prevpg; l->lower = BTDATAOFF; l->upper = t->bt_psize; l->flags = h->flags & P_TYPE; /* Fix up the previous pointer of the page after the split page. */ if (h->nextpg != P_INVALID) { if ((tp = mpool_get(t->bt_mp, h->nextpg, 0)) == NULL) { free(l); /* XXX mpool_free(t->bt_mp, r->pgno); */ return (NULL); } tp->prevpg = r->pgno; mpool_put(t->bt_mp, tp, MPOOL_DIRTY); } /* * Split right. The key/data pairs aren't sorted in the btree page so * it's simpler to copy the data from the split page onto two new pages * instead of copying half the data to the right page and compacting * the left page in place. Since the left page can't change, we have * to swap the original and the allocated left page after the split. */ tp = bt_psplit(t, h, l, r, skip, ilen); /* Move the new left page onto the old left page. */ memmove(h, l, t->bt_psize); if (tp == l) tp = h; free(l); *lp = h; *rp = r; return (tp); } /* * BT_ROOT -- Split the root page of a btree. * * Parameters: * t: tree * h: root page * lp: pointer to left page pointer * rp: pointer to right page pointer * skip: pointer to index to leave open * ilen: insert length * * Returns: * Pointer to page in which to insert or NULL on error. */ static PAGE * bt_root(BTREE *t, PAGE *h, PAGE **lp, PAGE **rp, indx_t *skip, size_t ilen) { PAGE *l, *r, *tp; pgno_t lnpg, rnpg; #ifdef STATISTICS ++bt_split; ++bt_rootsplit; #endif /* ifdef STATISTICS */ /* Put the new left and right pages for the split into place. */ if ((l = __bt_new(t, &lnpg)) == NULL || (r = __bt_new(t, &rnpg)) == NULL) return (NULL); l->pgno = lnpg; r->pgno = rnpg; l->nextpg = r->pgno; r->prevpg = l->pgno; l->prevpg = r->nextpg = P_INVALID; l->lower = r->lower = BTDATAOFF; l->upper = r->upper = t->bt_psize; l->flags = r->flags = h->flags & P_TYPE; /* Split the root page. */ tp = bt_psplit(t, h, l, r, skip, ilen); *lp = l; *rp = r; return (tp); } /* * BT_RROOT -- Fix up the recno root page after it has been split. * * Parameters: * t: tree * h: root page * l: left page * r: right page * * Returns: * RET_ERROR, RET_SUCCESS */ static int bt_rroot(BTREE *t, PAGE *h, PAGE *l, PAGE *r) { char *dest; /* Insert the left and right keys, set the header information. */ h->linp[0] = h->upper = t->bt_psize - NRINTERNAL; dest = (char *)h + h->upper; WR_RINTERNAL(dest, l->flags & P_RLEAF ? NEXTINDEX(l) : rec_total(l), l->pgno); h->linp[1] = h->upper -= NRINTERNAL; dest = (char *)h + h->upper; WR_RINTERNAL(dest, r->flags & P_RLEAF ? NEXTINDEX(r) : rec_total(r), r->pgno); h->lower = BTDATAOFF + 2 * sizeof(indx_t); /* Unpin the root page, set to recno internal page. */ h->flags &= ~P_TYPE; h->flags |= P_RINTERNAL; mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } /* * BT_BROOT -- Fix up the btree root page after it has been split. * * Parameters: * t: tree * h: root page * l: left page * r: right page * * Returns: * RET_ERROR, RET_SUCCESS */ static int bt_broot(BTREE *t, PAGE *h, PAGE *l, PAGE *r) { BINTERNAL *bi; BLEAF *bl; u_int32_t nbytes; char *dest; /* * If the root page was a leaf page, change it into an internal page. * We copy the key we split on (but not the key's data, in the case of * a leaf page) to the new root page. * * The btree comparison code guarantees that the left-most key on any * level of the tree is never used, so it doesn't need to be filled in. */ nbytes = NBINTERNAL(0); h->linp[0] = h->upper = t->bt_psize - nbytes; dest = (char *)h + h->upper; WR_BINTERNAL(dest, 0, l->pgno, 0); switch (h->flags & P_TYPE) { case P_BLEAF: bl = GETBLEAF(r, 0); nbytes = NBINTERNAL(bl->ksize); h->linp[1] = h->upper -= nbytes; dest = (char *)h + h->upper; WR_BINTERNAL(dest, bl->ksize, r->pgno, 0); memmove(dest, bl->bytes, bl->ksize); /* * If the key is on an overflow page, mark the overflow chain * so it isn't deleted when the leaf copy of the key is deleted. */ if (bl->flags & P_BIGKEY && bt_preserve(t, *(pgno_t *)bl->bytes) == RET_ERROR) return (RET_ERROR); break; case P_BINTERNAL: bi = GETBINTERNAL(r, 0); nbytes = NBINTERNAL(bi->ksize); h->linp[1] = h->upper -= nbytes; dest = (char *)h + h->upper; memmove(dest, bi, nbytes); ((BINTERNAL *)dest)->pgno = r->pgno; break; default: abort(); } /* There are two keys on the page. */ h->lower = BTDATAOFF + 2 * sizeof(indx_t); /* Unpin the root page, set to btree internal page. */ h->flags &= ~P_TYPE; h->flags |= P_BINTERNAL; mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } /* * BT_PSPLIT -- Do the real work of splitting the page. * * Parameters: * t: tree * h: page to be split * l: page to put lower half of data * r: page to put upper half of data * pskip: pointer to index to leave open * ilen: insert length * * Returns: * Pointer to page in which to insert. */ static PAGE * bt_psplit(BTREE *t, PAGE *h, PAGE *l, PAGE *r, indx_t *pskip, size_t ilen) { BINTERNAL *bi; BLEAF *bl; CURSOR *c; RLEAF *rl; PAGE *rval; void *src; indx_t full, half, nxt, off, skip, top, used; u_int32_t nbytes; int bigkeycnt, isbigkey; /* * Split the data to the left and right pages. Leave the skip index * open. Additionally, make some effort not to split on an overflow * key. This makes internal page processing faster and can save * space as overflow keys used by internal pages are never deleted. */ bigkeycnt = 0; skip = *pskip; full = t->bt_psize - BTDATAOFF; half = full / 2; used = 0; for (nxt = off = 0, top = NEXTINDEX(h); nxt < top; ++off) { if (skip == off) { nbytes = ilen; isbigkey = 0; /* XXX: not really known. */ } else switch (h->flags & P_TYPE) { case P_BINTERNAL: src = bi = GETBINTERNAL(h, nxt); nbytes = NBINTERNAL(bi->ksize); isbigkey = bi->flags & P_BIGKEY; break; case P_BLEAF: src = bl = GETBLEAF(h, nxt); nbytes = NBLEAF(bl); isbigkey = bl->flags & P_BIGKEY; break; case P_RINTERNAL: src = GETRINTERNAL(h, nxt); nbytes = NRINTERNAL; isbigkey = 0; break; case P_RLEAF: src = rl = GETRLEAF(h, nxt); nbytes = NRLEAF(rl); isbigkey = 0; break; default: abort(); } /* * If the key/data pairs are substantial fractions of the max * possible size for the page, it's possible to get situations * where we decide to try and copy too much onto the left page. * Make sure that doesn't happen. */ if ((skip <= off && used + nbytes + sizeof(indx_t) >= full) || nxt == top - 1) { --off; break; } /* Copy the key/data pair, if not the skipped index. */ if (skip != off) { ++nxt; l->linp[off] = l->upper -= nbytes; memmove((char *)l + l->upper, src, nbytes); } used += nbytes + sizeof(indx_t); if (used >= half) { if (!isbigkey || bigkeycnt == 3) break; else ++bigkeycnt; } } /* * Off is the last offset that's valid for the left page. * Nxt is the first offset to be placed on the right page. */ l->lower += (off + 1) * sizeof(indx_t); /* * If splitting the page that the cursor was on, the cursor has to be * adjusted to point to the same record as before the split. If the * cursor is at or past the skipped slot, the cursor is incremented by * one. If the cursor is on the right page, it is decremented by the * number of records split to the left page. */ c = &t->bt_cursor; if (F_ISSET(c, CURS_INIT) && c->pg.pgno == h->pgno) { if (c->pg.index >= skip) ++c->pg.index; if (c->pg.index < nxt) /* Left page. */ c->pg.pgno = l->pgno; else { /* Right page. */ c->pg.pgno = r->pgno; c->pg.index -= nxt; } } /* * If the skipped index was on the left page, just return that page. * Otherwise, adjust the skip index to reflect the new position on * the right page. */ if (skip <= off) { skip = MAX_PAGE_OFFSET; rval = l; } else { rval = r; *pskip -= nxt; } for (off = 0; nxt < top; ++off) { if (skip == nxt) { ++off; skip = MAX_PAGE_OFFSET; } switch (h->flags & P_TYPE) { case P_BINTERNAL: src = bi = GETBINTERNAL(h, nxt); nbytes = NBINTERNAL(bi->ksize); break; case P_BLEAF: src = bl = GETBLEAF(h, nxt); nbytes = NBLEAF(bl); break; case P_RINTERNAL: src = GETRINTERNAL(h, nxt); nbytes = NRINTERNAL; break; case P_RLEAF: src = rl = GETRLEAF(h, nxt); nbytes = NRLEAF(rl); break; default: abort(); } ++nxt; r->linp[off] = r->upper -= nbytes; memmove((char *)r + r->upper, src, nbytes); } r->lower += off * sizeof(indx_t); /* If the key is being appended to the page, adjust the index. */ if (skip == top) r->lower += sizeof(indx_t); return (rval); } /* * BT_PRESERVE -- Mark a chain of pages as used by an internal node. * * Chains of indirect blocks pointed to by leaf nodes get reclaimed when the * record that references them gets deleted. Chains pointed to by internal * pages never get deleted. This routine marks a chain as pointed to by an * internal page. * * Parameters: * t: tree * pg: page number of first page in the chain. * * Returns: * RET_SUCCESS, RET_ERROR. */ static int bt_preserve(BTREE *t, pgno_t pg) { PAGE *h; if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) return (RET_ERROR); h->flags |= P_PRESERVE; mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } /* * REC_TOTAL -- Return the number of recno entries below a page. * * Parameters: * h: page * * Returns: * The number of recno entries below a page. * * XXX * These values could be set by the bt_psplit routine. The problem is that the * entry has to be popped off of the stack etc. or the values have to be passed * all the way back to bt_split/bt_rroot and it's not very clean. */ static recno_t rec_total(PAGE *h) { recno_t recs; indx_t nxt, top; for (recs = 0, nxt = 0, top = NEXTINDEX(h); nxt < top; ++nxt) recs += GETRINTERNAL(h, nxt)->nrecs; return (recs); } ================================================ FILE: db/btree/bt_utils.c ================================================ /* $OpenBSD: bt_utils.c,v 1.13 2022/12/27 17:10:10 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "btree.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) /* * __bt_ret -- * Build return key/data pair. * * Parameters: * t: tree * e: key/data pair to be returned * key: user's key structure (NULL if not to be filled in) * rkey: memory area to hold key * data: user's data structure (NULL if not to be filled in) * rdata: memory area to hold data * copy: always copy the key/data item * * Returns: * RET_SUCCESS, RET_ERROR. */ int __bt_ret(BTREE *t, EPG *e, DBT *key, DBT *rkey, DBT *data, DBT *rdata, int copy) { BLEAF *bl; void *p; bl = GETBLEAF(e->page, e->index); /* * We must copy big keys/data to make them contiguous. Otherwise, * leave the page pinned and don't copy unless the user specified * concurrent access. */ if (key == NULL) goto dataonly; if (bl->flags & P_BIGKEY) { if (__ovfl_get(t, bl->bytes, &key->size, &rkey->data, &rkey->size)) return (RET_ERROR); key->data = rkey->data; } else if (copy || F_ISSET(t, B_DB_LOCK)) { if (bl->ksize > rkey->size) { p = realloc(rkey->data, bl->ksize); if (p == NULL) return (RET_ERROR); rkey->data = p; rkey->size = bl->ksize; } memmove(rkey->data, bl->bytes, bl->ksize); key->size = bl->ksize; key->data = rkey->data; } else { key->size = bl->ksize; key->data = bl->bytes; } dataonly: if (data == NULL) return (RET_SUCCESS); if (bl->flags & P_BIGDATA) { if (__ovfl_get(t, bl->bytes + bl->ksize, &data->size, &rdata->data, &rdata->size)) return (RET_ERROR); data->data = rdata->data; } else if (copy || F_ISSET(t, B_DB_LOCK)) { /* Use +1 in case the first record retrieved is 0 length. */ if (bl->dsize + 1 > rdata->size) { p = realloc(rdata->data, bl->dsize + 1); if (p == NULL) return (RET_ERROR); rdata->data = p; rdata->size = bl->dsize + 1; } memmove(rdata->data, bl->bytes + bl->ksize, bl->dsize); data->size = bl->dsize; data->data = rdata->data; } else { data->size = bl->dsize; data->data = bl->bytes + bl->ksize; } return (RET_SUCCESS); } /* * __BT_CMP -- Compare a key to a given record. * * Parameters: * t: tree * k1: DBT pointer of first arg to comparison * e: pointer to EPG for comparison * * Returns: * < 0 if k1 is < record * = 0 if k1 is = record * > 0 if k1 is > record */ int __bt_cmp(BTREE *t, const DBT *k1, EPG *e) { BINTERNAL *bi; BLEAF *bl; DBT k2; PAGE *h; void *bigkey; /* * The left-most key on internal pages, at any level of the tree, is * guaranteed by the following code to be less than any user key. * This saves us from having to update the leftmost key on an internal * page when the user inserts a new key in the tree smaller than * anything we've yet seen. */ h = e->page; if (e->index == 0 && h->prevpg == P_INVALID && !(h->flags & P_BLEAF)) return (1); bigkey = NULL; if (h->flags & P_BLEAF) { bl = GETBLEAF(h, e->index); if (bl->flags & P_BIGKEY) bigkey = bl->bytes; else { k2.data = bl->bytes; k2.size = bl->ksize; } } else { bi = GETBINTERNAL(h, e->index); if (bi->flags & P_BIGKEY) bigkey = bi->bytes; else { k2.data = bi->bytes; k2.size = bi->ksize; } } if (bigkey) { if (__ovfl_get(t, bigkey, &k2.size, &t->bt_rdata.data, &t->bt_rdata.size)) return (RET_ERROR); k2.data = t->bt_rdata.data; } return ((*t->bt_cmp)(k1, &k2)); } /* * __BT_DEFCMP -- Default comparison routine. * * Parameters: * a: DBT #1 * b: DBT #2 * * Returns: * < 0 if a is < b * = 0 if a is = b * > 0 if a is > b */ int __bt_defcmp(const DBT *a, const DBT *b) { size_t len; unsigned char *p1, *p2; /* * XXX * If a size_t doesn't fit in an int, this routine can lose. * What we need is a integral type which is guaranteed to be * larger than a size_t, and there is no such thing. */ len = MINIMUM(a->size, b->size); for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2) if (*p1 != *p2) return ((int)*p1 - (int)*p2); return ((int)a->size - (int)b->size); } /* * __BT_DEFPFX -- Default prefix routine. * * Parameters: * a: DBT #1 * b: DBT #2 * * Returns: * Number of bytes needed to distinguish b from a. */ size_t __bt_defpfx(const DBT *a, const DBT *b) { unsigned char *p1, *p2; size_t cnt, len; cnt = 1; len = MINIMUM(a->size, b->size); for (p1 = a->data, p2 = b->data; len--; ++p1, ++p2, ++cnt) if (*p1 != *p2) return (cnt); /* a->size must be <= b->size, or they wouldn't be in this order. */ return (a->size < b->size ? a->size + 1 : a->size); } ================================================ FILE: db/btree/btree.h ================================================ /* $OpenBSD: btree.h,v 1.7 2015/07/16 04:27:33 tedu Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. * * @(#)btree.h 8.11 (Berkeley) 8/17/94 */ /* Macros to set/clear/test flags. */ #define F_SET(p, f) (p)->flags |= (f) #define F_CLR(p, f) (p)->flags &= ~(f) #define F_ISSET(p, f) ((p)->flags & (f)) #include #define DEFMINKEYPAGE (2) /* Minimum keys per page */ #define MINCACHE (5) /* Minimum cached pages */ #define MINPSIZE (512) /* Minimum page size */ /* * Page 0 of a btree file contains a copy of the meta-data. This page is also * used as an out-of-band page, i.e. page pointers that point to nowhere point * to page 0. Page 1 is the root of the btree. */ #define P_INVALID 0 /* Invalid tree page number. */ #define P_META 0 /* Tree metadata page number. */ #define P_ROOT 1 /* Tree root page number. */ /* * There are five page layouts in the btree: btree internal pages (BINTERNAL), * btree leaf pages (BLEAF), recno internal pages (RINTERNAL), recno leaf pages * (RLEAF) and overflow pages. All five page types have a page header (PAGE). * This implementation requires that values within structures NOT be padded. * (ANSI C permits random padding.) If your compiler pads randomly you'll have * to do some work to get this package to run. */ typedef struct _page { pgno_t pgno; /* this page's page number */ pgno_t prevpg; /* left sibling */ pgno_t nextpg; /* right sibling */ #define P_BINTERNAL 0x01 /* btree internal page */ #define P_BLEAF 0x02 /* leaf page */ #define P_OVERFLOW 0x04 /* overflow page */ #define P_RINTERNAL 0x08 /* recno internal page */ #define P_RLEAF 0x10 /* leaf page */ #define P_TYPE 0x1f /* type mask */ #define P_PRESERVE 0x20 /* never delete this chain of pages */ u_int32_t flags; indx_t lower; /* lower bound of free space on page */ indx_t upper; /* upper bound of free space on page */ indx_t linp[1]; /* indx_t-aligned VAR. LENGTH DATA */ } PAGE; /* First and next index. */ #define BTDATAOFF \ (sizeof(pgno_t) + sizeof(pgno_t) + sizeof(pgno_t) + \ sizeof(u_int32_t) + sizeof(indx_t) + sizeof(indx_t)) #define NEXTINDEX(p) (((p)->lower - BTDATAOFF) / sizeof(indx_t)) /* * For pages other than overflow pages, there is an array of offsets into the * rest of the page immediately following the page header. Each offset is to * an item which is unique to the type of page. The h_lower offset is just * past the last filled-in index. The h_upper offset is the first item on the * page. Offsets are from the beginning of the page. * * If an item is too big to store on a single page, a flag is set and the item * is a { page, size } pair such that the page is the first page of an overflow * chain with size bytes of item. Overflow pages are simply bytes without any * external structure. * * The page number and size fields in the items are pgno_t-aligned so they can * be manipulated without copying. (This presumes that 32 bit items can be * manipulated on this system.) */ #define LALIGN(n) (((n) + sizeof(pgno_t) - 1) & ~(sizeof(pgno_t) - 1)) #define NOVFLSIZE (sizeof(pgno_t) + sizeof(u_int32_t)) /* * For the btree internal pages, the item is a key. BINTERNALs are {key, pgno} * pairs, such that the key compares less than or equal to all of the records * on that page. For a tree without duplicate keys, an internal page with two * consecutive keys, a and b, will have all records greater than or equal to a * and less than b stored on the page associated with a. Duplicate keys are * somewhat special and can cause duplicate internal and leaf page records and * some minor modifications of the above rule. */ typedef struct _binternal { u_int32_t ksize; /* key size */ pgno_t pgno; /* page number stored on */ #define P_BIGDATA 0x01 /* overflow data */ #define P_BIGKEY 0x02 /* overflow key */ unsigned char flags; char bytes[1]; /* data */ } BINTERNAL; /* Get the page's BINTERNAL structure at index indx. */ #define GETBINTERNAL(pg, indx) \ ((BINTERNAL *)((char *)(pg) + (pg)->linp[indx])) /* Get the number of bytes in the entry. */ #define NBINTERNAL(len) \ LALIGN(sizeof(u_int32_t) + sizeof(pgno_t) + sizeof(unsigned char) + (len)) /* Copy a BINTERNAL entry to the page. */ #define WR_BINTERNAL(p, size, pgno, flags) { \ *(u_int32_t *)p = size; \ p += sizeof(u_int32_t); \ *(pgno_t *)p = pgno; \ p += sizeof(pgno_t); \ *(unsigned char *)p = flags; \ p += sizeof(unsigned char); \ } /* * For the recno internal pages, the item is a page number with the number of * keys found on that page and below. */ typedef struct _rinternal { recno_t nrecs; /* number of records */ pgno_t pgno; /* page number stored below */ } RINTERNAL; /* Get the page's RINTERNAL structure at index indx. */ #define GETRINTERNAL(pg, indx) \ ((RINTERNAL *)((char *)(pg) + (pg)->linp[indx])) /* Get the number of bytes in the entry. */ #define NRINTERNAL \ LALIGN(sizeof(recno_t) + sizeof(pgno_t)) /* Copy a RINTERAL entry to the page. */ #define WR_RINTERNAL(p, nrecs, pgno) { \ *(recno_t *)p = nrecs; \ p += sizeof(recno_t); \ *(pgno_t *)p = pgno; \ } /* For the btree leaf pages, the item is a key and data pair. */ typedef struct _bleaf { u_int32_t ksize; /* size of key */ u_int32_t dsize; /* size of data */ unsigned char flags; /* P_BIGDATA, P_BIGKEY */ char bytes[1]; /* data */ } BLEAF; /* Get the page's BLEAF structure at index indx. */ #define GETBLEAF(pg, indx) \ ((BLEAF *)((char *)(pg) + (pg)->linp[indx])) /* Get the number of bytes in the entry. */ #define NBLEAF(p) NBLEAFDBT((p)->ksize, (p)->dsize) /* Get the number of bytes in the user's key/data pair. */ #define NBLEAFDBT(ksize, dsize) \ LALIGN(sizeof(u_int32_t) + sizeof(u_int32_t) + sizeof(unsigned char) + \ (ksize) + (dsize)) /* Copy a BLEAF entry to the page. */ #define WR_BLEAF(p, key, data, flags) { \ *(u_int32_t *)p = key->size; \ p += sizeof(u_int32_t); \ *(u_int32_t *)p = data->size; \ p += sizeof(u_int32_t); \ *(unsigned char *)p = flags; \ p += sizeof(unsigned char); \ memmove(p, key->data, key->size); \ p += key->size; \ memmove(p, data->data, data->size); \ } /* For the recno leaf pages, the item is a data entry. */ typedef struct _rleaf { u_int32_t dsize; /* size of data */ unsigned char flags; /* P_BIGDATA */ char bytes[1]; } RLEAF; /* Get the page's RLEAF structure at index indx. */ #define GETRLEAF(pg, indx) \ ((RLEAF *)((char *)(pg) + (pg)->linp[indx])) /* Get the number of bytes in the entry. */ #define NRLEAF(p) NRLEAFDBT((p)->dsize) /* Get the number of bytes from the user's data. */ #define NRLEAFDBT(dsize) \ LALIGN(sizeof(u_int32_t) + sizeof(unsigned char) + (dsize)) /* Copy a RLEAF entry to the page. */ #define WR_RLEAF(p, data, flags) { \ *(u_int32_t *)p = data->size; \ p += sizeof(u_int32_t); \ *(unsigned char *)p = flags; \ p += sizeof(unsigned char); \ memmove(p, data->data, data->size); \ } /* * A record in the tree is either a pointer to a page and an index in the page * or a page number and an index. These structures are used as a cursor, stack * entry and search returns as well as to pass records to other routines. * * One comment about searches. Internal page searches must find the largest * record less than key in the tree so that descents work. Leaf page searches * must find the smallest record greater than key so that the returned index * is the record's correct position for insertion. */ typedef struct _epgno { pgno_t pgno; /* the page number */ indx_t index; /* the index on the page */ } EPGNO; typedef struct _epg { PAGE *page; /* the (pinned) page */ indx_t index; /* the index on the page */ } EPG; /* * About cursors. The cursor (and the page that contained the key/data pair * that it referenced) can be deleted, which makes things a bit tricky. If * there are no duplicates of the cursor key in the tree (i.e. B_NODUPS is set * or there simply aren't any duplicates of the key) we copy the key that it * referenced when it's deleted, and reacquire a new cursor key if the cursor * is used again. If there are duplicates keys, we move to the next/previous * key, and set a flag so that we know what happened. NOTE: if duplicate (to * the cursor) keys are added to the tree during this process, it is undefined * if they will be returned or not in a cursor scan. * * The flags determine the possible states of the cursor: * * CURS_INIT The cursor references *something*. * CURS_ACQUIRE The cursor was deleted, and a key has been saved so that * we can reacquire the right position in the tree. * CURS_AFTER, CURS_BEFORE * The cursor was deleted, and now references a key/data pair * that has not yet been returned, either before or after the * deleted key/data pair. * XXX * This structure is broken out so that we can eventually offer multiple * cursors as part of the DB interface. */ typedef struct _cursor { EPGNO pg; /* B: Saved tree reference. */ DBT key; /* B: Saved key, or key.data == NULL. */ recno_t rcursor; /* R: recno cursor (1-based) */ #define CURS_ACQUIRE 0x01 /* B: Cursor needs to be reacquired. */ #define CURS_AFTER 0x02 /* B: Unreturned cursor after key. */ #define CURS_BEFORE 0x04 /* B: Unreturned cursor before key. */ #define CURS_INIT 0x08 /* RB: Cursor initialized. */ u_int8_t flags; } CURSOR; /* * The metadata of the tree. The nrecs field is used only by the RECNO code. * This is because the btree doesn't really need it and it requires that every * put or delete call modify the metadata. */ typedef struct _btmeta { u_int32_t magic; /* magic number */ u_int32_t version; /* version */ u_int32_t psize; /* page size */ u_int32_t free; /* page number of first free page */ u_int32_t nrecs; /* R: number of records */ #define SAVEMETA (B_NODUPS | R_RECNO) u_int32_t flags; /* bt_flags & SAVEMETA */ } BTMETA; /* The in-memory btree/recno data structure. */ typedef struct _btree { MPOOL *bt_mp; /* memory pool cookie */ DB *bt_dbp; /* pointer to enclosing DB */ EPG bt_cur; /* current (pinned) page */ PAGE *bt_pinned; /* page pinned across calls */ CURSOR bt_cursor; /* cursor */ #define BT_PUSH(t, p, i) { \ t->bt_sp->pgno = p; \ t->bt_sp->index = i; \ ++t->bt_sp; \ } #define BT_POP(t) (t->bt_sp == t->bt_stack ? NULL : --t->bt_sp) #define BT_CLR(t) (t->bt_sp = t->bt_stack) EPGNO bt_stack[50]; /* stack of parent pages */ EPGNO *bt_sp; /* current stack pointer */ DBT bt_rkey; /* returned key */ DBT bt_rdata; /* returned data */ int bt_fd; /* tree file descriptor */ pgno_t bt_free; /* next free page */ u_int32_t bt_psize; /* page size */ indx_t bt_ovflsize; /* cut-off for key/data overflow */ int bt_lorder; /* byte order */ /* sorted order */ enum { NOT, BACK, FORWARD } bt_order; EPGNO bt_last; /* last insert */ /* B: key comparison function */ int (*bt_cmp)(const DBT *, const DBT *); /* B: prefix comparison function */ size_t (*bt_pfx)(const DBT *, const DBT *); /* R: recno input function */ int (*bt_irec)(struct _btree *, recno_t); FILE *bt_rfp; /* R: record FILE pointer */ int bt_rfd; /* R: record file descriptor */ caddr_t bt_cmap; /* R: current point in mapped space */ caddr_t bt_smap; /* R: start of mapped space */ caddr_t bt_emap; /* R: end of mapped space */ size_t bt_msize; /* R: size of mapped region. */ recno_t bt_nrecs; /* R: number of records */ size_t bt_reclen; /* R: fixed record length */ unsigned char bt_bval; /* R: delimiting byte/pad character */ /* * NB: * B_NODUPS and R_RECNO are stored on disk, and may not be changed. */ #define B_INMEM 0x00001 /* in-memory tree */ #define B_METADIRTY 0x00002 /* need to write metadata */ #define B_MODIFIED 0x00004 /* tree modified */ #define B_NEEDSWAP 0x00008 /* if byte order requires swapping */ #define B_RDONLY 0x00010 /* read-only tree */ #define B_NODUPS 0x00020 /* no duplicate keys permitted */ #define R_RECNO 0x00080 /* record oriented tree */ #define R_CLOSEFP 0x00040 /* opened a file pointer */ #define R_EOF 0x00100 /* end of input file reached. */ #define R_FIXLEN 0x00200 /* fixed length records */ #define R_INMEM 0x00800 /* in-memory file */ #define R_MODIFIED 0x01000 /* modified file */ #define R_RDONLY 0x02000 /* read-only file */ #define B_DB_LOCK 0x04000 /* DB_LOCK specified. */ #define B_DB_SHMEM 0x08000 /* DB_SHMEM specified. */ #define B_DB_TXN 0x10000 /* DB_TXN specified. */ u_int32_t flags; } BTREE; #include "extern.h" ================================================ FILE: db/btree/extern.h ================================================ /* $OpenBSD: extern.h,v 1.8 2015/08/27 04:37:09 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)extern.h 8.10 (Berkeley) 7/20/94 */ __BEGIN_HIDDEN_DECLS int __bt_close(DB *); int __bt_cmp(BTREE *, const DBT *, EPG *); int __bt_defcmp(const DBT *, const DBT *); size_t __bt_defpfx(const DBT *, const DBT *); int __bt_delete(const DB *, const DBT *, unsigned int); int __bt_dleaf(BTREE *, const DBT *, PAGE *, unsigned int); int __bt_fd(const DB *); int __bt_free(BTREE *, PAGE *); int __bt_get(const DB *, const DBT *, DBT *, unsigned int); PAGE *__bt_new(BTREE *, pgno_t *); void __bt_pgin(void *, pgno_t, void *); void __bt_pgout(void *, pgno_t, void *); int __bt_put(const DB *dbp, DBT *, const DBT *, unsigned int); int __bt_ret(BTREE *, EPG *, DBT *, DBT *, DBT *, DBT *, int); EPG *__bt_search(BTREE *, const DBT *, int *); int __bt_seq(const DB *, DBT *, DBT *, unsigned int); void __bt_setcur(BTREE *, pgno_t, unsigned int); int __bt_split(BTREE *, PAGE *, const DBT *, const DBT *, int, size_t, u_int32_t); int __bt_sync(const DB *, unsigned int); int __ovfl_delete(BTREE *, void *); int __ovfl_get(BTREE *, void *, size_t *, void **, size_t *); int __ovfl_put(BTREE *, const DBT *, pgno_t *); #ifdef DEBUG void __bt_dnpage(DB *, pgno_t); void __bt_dpage(PAGE *); void __bt_dump(DB *); #endif /* ifdef DEBUG */ #ifdef STATISTICS void __bt_stat(DB *); #endif /* ifdef STATISTICS */ __END_HIDDEN_DECLS ================================================ FILE: db/db/db.c ================================================ /* $OpenBSD: db.c,v 1.13 2015/09/05 11:28:35 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #include #undef open static int __dberr(void); DB * dbopen(const char *fname, int flags, int mode, DBTYPE type, const void *openinfo) { #define DB_FLAGS (DB_LOCK | DB_SHMEM | DB_TXN) #define USE_OPEN_FLAGS \ (O_CREAT | O_EXCL | O_EXLOCK | O_NOFOLLOW | O_NONBLOCK | \ O_ACCMODE | O_SHLOCK | O_SYNC | O_TRUNC) if (((flags & O_ACCMODE) == O_RDONLY || (flags & O_ACCMODE) == O_RDWR) && (flags & ~(USE_OPEN_FLAGS | DB_FLAGS)) == 0) switch (type) { case DB_BTREE: return (__bt_open(fname, flags & USE_OPEN_FLAGS, mode, openinfo, flags & DB_FLAGS)); case DB_HASH: return (__hash_open(fname, flags & USE_OPEN_FLAGS, mode, openinfo, flags & DB_FLAGS)); case DB_RECNO: return (__rec_open(fname, flags & USE_OPEN_FLAGS, mode, openinfo, flags & DB_FLAGS)); } errno = EINVAL; return (NULL); } DEF_WEAK(dbopen); static int __dberr(void) { return (RET_ERROR); } /* * __DBPANIC -- Stop. * * Parameters: * dbp: pointer to the DB structure. */ void __dbpanic(DB *dbp) { /* The only thing that can succeed is a close. */ dbp->del = (int (*)(const struct __db *, const DBT*, unsigned int))__dberr; dbp->fd = (int (*)(const struct __db *))__dberr; dbp->get = (int (*)(const struct __db *, const DBT*, DBT *, unsigned int))__dberr; dbp->put = (int (*)(const struct __db *, DBT *, const DBT *, unsigned int))__dberr; dbp->seq = (int (*)(const struct __db *, DBT *, DBT *, unsigned int))__dberr; dbp->sync = (int (*)(const struct __db *, unsigned int))__dberr; } ================================================ FILE: db/hash/bsd_ndbm.h ================================================ /* $OpenBSD: ndbm.h,v 1.6 2004/05/03 17:27:50 millert Exp $ */ /* $NetBSD: ndbm.h,v 1.6 1995/07/20 23:31:11 jtc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. * * @(#)ndbm.h 8.1 (Berkeley) 6/2/93 */ #ifndef _NDBM_H_ # define _NDBM_H_ # include "../../include/compat.h" # include # include # undef open /* Map dbm interface onto db(3). */ # define DBM_RDONLY O_RDONLY /* Flags to dbm_store(). */ # define DBM_INSERT 0 # define DBM_REPLACE 1 /* * The db(3) support for ndbm(3) always appends this suffix to the * file name to avoid overwriting the user's original database. */ # define DBM_SUFFIX ".db" typedef struct { void *dptr; size_t dsize; } datum; typedef DB DBM; # define dbm_pagfno(a) DBM_PAGFNO_NOT_AVAILABLE __BEGIN_DECLS int dbm_clearerr(DBM *); void dbm_close(DBM *); int dbm_delete(DBM *, datum); int dbm_error(DBM *); datum dbm_fetch(DBM *, datum); datum dbm_firstkey(DBM *); datum dbm_nextkey(DBM *); DBM *dbm_open(const char *, int, mode_t); int dbm_store(DBM *, datum, datum, int); int dbm_dirfno(DBM *); int dbm_rdonly(DBM *); __END_DECLS #endif /* !_NDBM_H_ */ ================================================ FILE: db/hash/extern.h ================================================ /* $OpenBSD: extern.h,v 1.9 2016/05/29 20:47:49 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)extern.h 8.4 (Berkeley) 6/16/94 */ __BEGIN_HIDDEN_DECLS BUFHEAD *__add_ovflpage(HTAB *, BUFHEAD *); int __addel(HTAB *, BUFHEAD *, const DBT *, const DBT *); int __big_delete(HTAB *, BUFHEAD *); int __big_insert(HTAB *, BUFHEAD *, const DBT *, const DBT *); int __big_keydata(HTAB *, BUFHEAD *, DBT *, DBT *, int); int __big_return(HTAB *, BUFHEAD *, int, DBT *, int); int __big_split(HTAB *, BUFHEAD *, BUFHEAD *, BUFHEAD *, int, u_int32_t, SPLIT_RETURN *); int __buf_free(HTAB *, int, int); void __buf_init(HTAB *, int); u_int32_t __call_hash(HTAB *, char *, int); int __delpair(HTAB *, BUFHEAD *, int); int __expand_table(HTAB *); int __find_bigpair(HTAB *, BUFHEAD *, int, char *, int); u_int16_t __find_last_page(HTAB *, BUFHEAD **); void __free_ovflpage(HTAB *, BUFHEAD *); BUFHEAD *__get_buf(HTAB *, u_int32_t, BUFHEAD *, int); int __get_page(HTAB *, char *, u_int32_t, int, int, int); int __ibitmap(HTAB *, int, int, int); u_int32_t __log2(u_int32_t); int __put_page(HTAB *, char *, u_int32_t, int, int); void __reclaim_buf(HTAB *, BUFHEAD *); int __split_page(HTAB *, u_int32_t, u_int32_t); #ifdef HASH_STATISTICS extern int hash_accesses, hash_collisions, hash_expansions, hash_overflows; #endif /* ifdef HASH_STATISTICS */ __END_HIDDEN_DECLS ================================================ FILE: db/hash/hash.c ================================================ /* $OpenBSD: hash.c,v 1.29 2016/09/21 04:38:56 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ #ifndef EFTYPE # define EFTYPE ENOTSUP #endif /* ifndef EFTYPE */ #include "../../include/compat.h" #include #include #include #include #include #include #include #ifdef DEBUG # include #endif /* ifdef DEBUG */ #include #include #include "hash.h" #include "page.h" #include "extern.h" #undef open #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) #if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) # define BIG_ENDIAN 4321 # define LITTLE_ENDIAN 1234 #endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */ #if ( !defined(BYTE_ORDER) && defined(_AIX) ) # if ( defined(__powerpc__) || defined(__PPC__) \ || defined(_ARCH_PPC) ) # define BYTE_ORDER BIG_ENDIAN /* Assume AIX/PPC is big-endian */ # endif /* if ( defined(__powerpc__) || defined(__PPC__) || defined(_ARCH_PPC) ) */ #endif /* if ( !defined(BYTE_ORDER) && defined(_AIX) ) */ static int alloc_segs(HTAB *, int); static int flush_meta(HTAB *); static int hash_access(HTAB *, ACTION, DBT *, DBT *); static int hash_close(DB *); static int hash_delete(const DB *, const DBT *, u_int32_t); static int hash_fd(const DB *); static int hash_get(const DB *, const DBT *, DBT *, u_int32_t); static int hash_put(const DB *, DBT *, const DBT *, u_int32_t); static void *hash_realloc(SEGMENT **, int, int); static int hash_seq(const DB *, DBT *, DBT *, u_int32_t); static int hash_sync(const DB *, u_int32_t); static int hdestroy(HTAB *); static HTAB *init_hash(HTAB *, const char *, const HASHINFO *); static int init_htab(HTAB *, int); #if BYTE_ORDER == LITTLE_ENDIAN static void swap_header(HTAB *); static void swap_header_copy(HASHHDR *, HASHHDR *); #endif /* if BYTE_ORDER == LITTLE_ENDIAN */ /* Fast arithmetic, relying on powers of 2, */ #define MOD(x, y) ((x) & ((y) - 1)) #define RETURN_ERROR(ERR, LOC) { save_errno = ERR; goto LOC; } /* Return values */ #define SUCCESS (0) #define ERROR (-1) #define ABNORMAL (1) #ifdef HASH_STATISTICS int hash_accesses, hash_collisions, hash_expansions, hash_overflows; #endif /* ifdef HASH_STATISTICS */ /************************** INTERFACE ROUTINES ***************************/ /* OPEN/CLOSE */ DB * __hash_open(const char *file, int flags, int mode, const HASHINFO *info, /* Special directives for create */ int dflags) { HTAB *hashp; struct stat statbuf; DB *dbp; int bpages, hdrsize, new_table, nsegs, save_errno; if ((flags & O_ACCMODE) == O_WRONLY) { errno = EINVAL; return (NULL); } if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB)))) return (NULL); hashp->fp = -1; /* * Even if user wants write only, we need to be able to read * the actual file, so we need to open it read/write. But, the * field in the hashp structure needs to be accurate so that * we can check accesses. */ hashp->flags = flags; if (file) { if ((hashp->fp = open(file, flags | O_CLOEXEC, mode)) == -1) RETURN_ERROR(errno, error0); new_table = fstat(hashp->fp, &statbuf) == 0 && statbuf.st_size == 0 && (flags & O_ACCMODE) != O_RDONLY; } else new_table = 1; if (new_table) { if (!(hashp = init_hash(hashp, file, info))) RETURN_ERROR(errno, error1); } else { /* Table already exists */ if (info && info->hash) hashp->hash = info->hash; else hashp->hash = __default_hash; hdrsize = read(hashp->fp, &hashp->hdr, sizeof(HASHHDR)); #if BYTE_ORDER == LITTLE_ENDIAN swap_header(hashp); #endif /* if BYTE_ORDER == LITTLE_ENDIAN */ if (hdrsize == -1) RETURN_ERROR(errno, error1); if (hdrsize != sizeof(HASHHDR)) RETURN_ERROR(EFTYPE, error1); /* Verify file type, versions and hash function */ if (hashp->MAGIC != HASHMAGIC) RETURN_ERROR(EFTYPE, error1); #define OLDHASHVERSION 1 if (hashp->VERSION != HASHVERSION && hashp->VERSION != OLDHASHVERSION) RETURN_ERROR(EFTYPE, error1); if (hashp->hash(CHARKEY, sizeof(CHARKEY)) != hashp->H_CHARKEY) RETURN_ERROR(EFTYPE, error1); /* * Figure out how many segments we need. Max_Bucket is the * maximum bucket number, so the number of buckets is * max_bucket + 1. */ nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) / hashp->SGSIZE; if (alloc_segs(hashp, nsegs)) /* * If alloc_segs fails, table will have been destroyed * and errno will have been set. */ return (NULL); /* Read in bitmaps */ bpages = (hashp->SPARES[hashp->OVFL_POINT] + (hashp->BSIZE << BYTE_SHIFT) - 1) >> (hashp->BSHIFT + BYTE_SHIFT); hashp->nmaps = bpages; (void)memset(&hashp->mapp[0], 0, bpages * sizeof(u_int32_t *)); } /* Initialize Buffer Manager */ if (info && info->cachesize) __buf_init(hashp, info->cachesize); else __buf_init(hashp, DEF_BUFSIZE); hashp->new_file = new_table; hashp->save_file = file && (hashp->flags & O_RDWR); hashp->cbucket = -1; if (!(dbp = (DB *)malloc(sizeof(DB)))) { save_errno = errno; hdestroy(hashp); errno = save_errno; return (NULL); } dbp->internal = hashp; dbp->close = hash_close; dbp->del = hash_delete; dbp->fd = hash_fd; dbp->get = hash_get; dbp->put = hash_put; dbp->seq = hash_seq; dbp->sync = hash_sync; dbp->type = DB_HASH; #ifdef DEBUG (void)fprintf(stderr, "%s\n%s%p\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%d\n%s%x\n%s%x\n%s%d\n%s%d\n", "init_htab:", "TABLE POINTER ", hashp, "BUCKET SIZE ", hashp->BSIZE, "BUCKET SHIFT ", hashp->BSHIFT, "DIRECTORY SIZE ", hashp->DSIZE, "SEGMENT SIZE ", hashp->SGSIZE, "SEGMENT SHIFT ", hashp->SSHIFT, "FILL FACTOR ", hashp->FFACTOR, "MAX BUCKET ", hashp->MAX_BUCKET, "OVFL POINT ", hashp->OVFL_POINT, "LAST FREED ", hashp->LAST_FREED, "HIGH MASK ", hashp->HIGH_MASK, "LOW MASK ", hashp->LOW_MASK, "NSEGS ", hashp->nsegs, "NKEYS ", hashp->NKEYS); #endif /* ifdef DEBUG */ #ifdef HASH_STATISTICS hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0; #endif /* ifdef HASH_STATISTICS */ return (dbp); error1: if (hashp != NULL) (void)close(hashp->fp); error0: free(hashp); errno = save_errno; return (NULL); } static int hash_close(DB *dbp) { HTAB *hashp; int retval; if (!dbp) return (ERROR); hashp = (HTAB *)dbp->internal; retval = hdestroy(hashp); free(dbp); return (retval); } static int hash_fd(const DB *dbp) { HTAB *hashp; if (!dbp) return (ERROR); hashp = (HTAB *)dbp->internal; if (hashp->fp == -1) { errno = ENOENT; return (-1); } return (hashp->fp); } /************************** LOCAL CREATION ROUTINES **********************/ static HTAB * init_hash(HTAB *hashp, const char *file, const HASHINFO *info) { struct stat statbuf; int nelem; nelem = 1; hashp->NKEYS = 0; hashp->LORDER = BYTE_ORDER; hashp->BSIZE = DEF_BUCKET_SIZE; hashp->BSHIFT = DEF_BUCKET_SHIFT; hashp->SGSIZE = DEF_SEGSIZE; hashp->SSHIFT = DEF_SEGSIZE_SHIFT; hashp->DSIZE = DEF_DIRSIZE; hashp->FFACTOR = DEF_FFACTOR; hashp->hash = __default_hash; memset(hashp->SPARES, 0, sizeof(hashp->SPARES)); memset(hashp->BITMAPS, 0, sizeof (hashp->BITMAPS)); /* Fix bucket size to be optimal for file system */ if (file != NULL) { if (stat(file, &statbuf)) return (NULL); hashp->BSIZE = statbuf.st_blksize; hashp->BSHIFT = __log2(hashp->BSIZE); } if (info) { if (info->bsize) { /* Round pagesize up to power of 2 */ hashp->BSHIFT = __log2(info->bsize); hashp->BSIZE = 1 << hashp->BSHIFT; if (hashp->BSIZE > MAX_BSIZE) { errno = EINVAL; return (NULL); } } if (info->ffactor) hashp->FFACTOR = info->ffactor; if (info->hash) hashp->hash = info->hash; if (info->nelem) nelem = info->nelem; if (info->lorder) { if (info->lorder != BIG_ENDIAN && info->lorder != LITTLE_ENDIAN) { errno = EINVAL; return (NULL); } hashp->LORDER = info->lorder; } } /* init_htab should destroy the table and set errno if it fails */ if (init_htab(hashp, nelem)) return (NULL); else return (hashp); } /* * This calls alloc_segs which may run out of memory. Alloc_segs will destroy * the table and set errno, so we just pass the error information along. * * Returns 0 on No Error */ static int init_htab(HTAB *hashp, int nelem) { int nbuckets, nsegs, l2; /* * Divide number of elements by the fill factor and determine a * desired number of buckets. Allocate space for the next greater * power of two number of buckets. */ nelem = (nelem - 1) / hashp->FFACTOR + 1; l2 = __log2(MAXIMUM(nelem, 2)); nbuckets = 1 << l2; hashp->SPARES[l2] = l2 + 1; hashp->SPARES[l2 + 1] = l2 + 1; hashp->OVFL_POINT = l2; hashp->LAST_FREED = 2; /* First bitmap page is at: splitpoint l2 page offset 1 */ if (__ibitmap(hashp, OADDR_OF(l2, 1), l2 + 1, 0)) return (-1); hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1; hashp->HIGH_MASK = (nbuckets << 1) - 1; hashp->HDRPAGES = ((MAXIMUM(sizeof(HASHHDR), MINHDRSIZE) - 1) >> hashp->BSHIFT) + 1; nsegs = (nbuckets - 1) / hashp->SGSIZE + 1; nsegs = 1 << __log2(nsegs); if (nsegs > hashp->DSIZE) hashp->DSIZE = nsegs; return (alloc_segs(hashp, nsegs)); } /********************** DESTROY/CLOSE ROUTINES ************************/ /* * Flushes any changes to the file if necessary and destroys the hashp * structure, freeing all allocated space. */ static int hdestroy(HTAB *hashp) { int i, save_errno; save_errno = 0; #ifdef HASH_STATISTICS (void)fprintf(stderr, "hdestroy: accesses %lu collisions %lu\n", (unsigned long)hash_accesses, (unsigned long)hash_collisions); (void)fprintf(stderr, "hdestroy: expansions %lu\n", (unsigned long)hash_expansions); (void)fprintf(stderr, "hdestroy: overflows %lu\n", (unsigned long)hash_overflows); (void)fprintf(stderr, "keys %lu maxp %lu segmentcount %lu\n", (unsigned long)hashp->NKEYS, (unsigned long)hashp->MAX_BUCKET, (unsigned long)hashp->nsegs); for (i = 0; i < NCACHED; i++) (void)fprintf(stderr, "spares[%d] = %d\n", i, hashp->SPARES[i]); #endif /* ifdef HASH_STATISTICS */ /* * Call on buffer manager to free buffers, and if required, * write them to disk. */ if (__buf_free(hashp, 1, hashp->save_file)) save_errno = errno; if (hashp->dir) { free(*hashp->dir); /* Free initial segments */ /* Free extra segments */ while (hashp->exsegs--) free(hashp->dir[--hashp->nsegs]); free(hashp->dir); } if (flush_meta(hashp) && !save_errno) save_errno = errno; /* Free Bigmaps */ for (i = 0; i < hashp->nmaps; i++) free(hashp->mapp[i]); free(hashp->tmp_key); free(hashp->tmp_buf); if (hashp->fp != -1) (void)close(hashp->fp); free(hashp); if (save_errno) { errno = save_errno; return (ERROR); } return (SUCCESS); } /* * Write modified pages to disk * * Returns: * 0 == OK * -1 ERROR */ static int hash_sync(const DB *dbp, u_int32_t flags) { HTAB *hashp; if (flags != 0) { errno = EINVAL; return (ERROR); } if (!dbp) return (ERROR); hashp = (HTAB *)dbp->internal; if (!hashp->save_file) return (0); if (__buf_free(hashp, 0, 1) || flush_meta(hashp)) return (ERROR); hashp->new_file = 0; return (0); } /* * Returns: * 0 == OK * -1 indicates that errno should be set */ static int flush_meta(HTAB *hashp) { HASHHDR *whdrp; #if BYTE_ORDER == LITTLE_ENDIAN HASHHDR whdr; #endif /* if BYTE_ORDER == LITTLE_ENDIAN */ int fp, i, wsize; if (!hashp->save_file) return (0); hashp->MAGIC = HASHMAGIC; hashp->VERSION = HASHVERSION; hashp->H_CHARKEY = hashp->hash(CHARKEY, sizeof(CHARKEY)); fp = hashp->fp; whdrp = &hashp->hdr; (void)whdrp; #if BYTE_ORDER == LITTLE_ENDIAN whdrp = &whdr; swap_header_copy(&hashp->hdr, whdrp); #endif /* if BYTE_ORDER == LITTLE_ENDIAN */ if ((wsize = pwrite(fp, whdrp, sizeof(HASHHDR), 0)) == -1) return (-1); else if (wsize != sizeof(HASHHDR)) { errno = EFTYPE; hashp->err = errno; return (-1); } for (i = 0; i < NCACHED; i++) if (hashp->mapp[i]) if (__put_page(hashp, (char *)hashp->mapp[i], hashp->BITMAPS[i], 0, 1)) return (-1); return (0); } /*******************************SEARCH ROUTINES *****************************/ /* * All the access routines return * * Returns: * 0 on SUCCESS * 1 to indicate an external ERROR (i.e. key not found, etc) * -1 to indicate an internal ERROR (i.e. out of memory, etc) */ static int hash_get(const DB *dbp, const DBT *key, DBT *data, u_int32_t flag) { HTAB *hashp; hashp = (HTAB *)dbp->internal; if (flag) { hashp->err = errno = EINVAL; return (ERROR); } return (hash_access(hashp, HASH_GET, (DBT *)key, data)); } static int hash_put(const DB *dbp, DBT *key, const DBT *data, u_int32_t flag) { HTAB *hashp; hashp = (HTAB *)dbp->internal; if (flag && flag != R_NOOVERWRITE) { hashp->err = errno = EINVAL; return (ERROR); } if ((hashp->flags & O_ACCMODE) == O_RDONLY) { hashp->err = errno = EPERM; return (ERROR); } return (hash_access(hashp, flag == R_NOOVERWRITE ? HASH_PUTNEW : HASH_PUT, (DBT *)key, (DBT *)data)); } static int hash_delete(const DB *dbp, const DBT *key, u_int32_t flag) /* Ignored */ { HTAB *hashp; hashp = (HTAB *)dbp->internal; if (flag && flag != R_CURSOR) { hashp->err = errno = EINVAL; return (ERROR); } if ((hashp->flags & O_ACCMODE) == O_RDONLY) { hashp->err = errno = EPERM; return (ERROR); } return (hash_access(hashp, HASH_DELETE, (DBT *)key, NULL)); } /* * Assume that hashp has been set in wrapper routine. */ static int hash_access(HTAB *hashp, ACTION action, DBT *key, DBT *val) { BUFHEAD *rbufp; BUFHEAD *bufp, *save_bufp; u_int16_t *bp; int n, ndx, off, size; char *kp; u_int16_t pageno; #ifdef HASH_STATISTICS hash_accesses++; #endif /* ifdef HASH_STATISTICS */ off = hashp->BSIZE; size = key->size; kp = (char *)key->data; rbufp = __get_buf(hashp, __call_hash(hashp, kp, size), NULL, 0); if (!rbufp) return (ERROR); save_bufp = rbufp; /* Pin the bucket chain */ rbufp->flags |= BUF_PIN; for (bp = (u_int16_t *)rbufp->page, n = *bp++, ndx = 1; ndx < n;) if (bp[1] >= REAL_KEY) { /* Real key/data pair */ if (size == off - *bp && memcmp(kp, rbufp->page + *bp, size) == 0) goto found; off = bp[1]; #ifdef HASH_STATISTICS hash_collisions++; #endif /* ifdef HASH_STATISTICS */ bp += 2; ndx += 2; } else if (bp[1] == OVFLPAGE) { rbufp = __get_buf(hashp, *bp, rbufp, 0); if (!rbufp) { save_bufp->flags &= ~BUF_PIN; return (ERROR); } /* FOR LOOP INIT */ bp = (u_int16_t *)rbufp->page; n = *bp++; ndx = 1; off = hashp->BSIZE; } else if (bp[1] < REAL_KEY) { if ((ndx = __find_bigpair(hashp, rbufp, ndx, kp, size)) > 0) goto found; if (ndx == -2) { bufp = rbufp; if (!(pageno = __find_last_page(hashp, &bufp))) { ndx = 0; (void)ndx; rbufp = bufp; break; /* FOR */ } rbufp = __get_buf(hashp, pageno, bufp, 0); if (!rbufp) { save_bufp->flags &= ~BUF_PIN; return (ERROR); } /* FOR LOOP INIT */ bp = (u_int16_t *)rbufp->page; n = *bp++; ndx = 1; off = hashp->BSIZE; } else { save_bufp->flags &= ~BUF_PIN; return (ERROR); } } /* Not found */ switch (action) { case HASH_PUT: case HASH_PUTNEW: if (__addel(hashp, rbufp, key, val)) { save_bufp->flags &= ~BUF_PIN; return (ERROR); } else { save_bufp->flags &= ~BUF_PIN; return (SUCCESS); } case HASH_GET: case HASH_DELETE: default: save_bufp->flags &= ~BUF_PIN; return (ABNORMAL); } found: switch (action) { case HASH_PUTNEW: save_bufp->flags &= ~BUF_PIN; return (ABNORMAL); case HASH_GET: bp = (u_int16_t *)rbufp->page; if (bp[ndx + 1] < REAL_KEY) { if (__big_return(hashp, rbufp, ndx, val, 0)) return (ERROR); } else { val->data = (unsigned char *)rbufp->page + (int)bp[ndx + 1]; val->size = bp[ndx] - bp[ndx + 1]; } break; case HASH_PUT: if ((__delpair(hashp, rbufp, ndx)) || (__addel(hashp, rbufp, key, val))) { save_bufp->flags &= ~BUF_PIN; return (ERROR); } break; case HASH_DELETE: if (__delpair(hashp, rbufp, ndx)) return (ERROR); break; default: abort(); } save_bufp->flags &= ~BUF_PIN; return (SUCCESS); } static int hash_seq(const DB *dbp, DBT *key, DBT *data, u_int32_t flag) { u_int32_t bucket; BUFHEAD *bufp; HTAB *hashp; u_int16_t *bp, ndx; hashp = (HTAB *)dbp->internal; if (flag && flag != R_FIRST && flag != R_NEXT) { hashp->err = errno = EINVAL; return (ERROR); } #ifdef HASH_STATISTICS hash_accesses++; #endif /* ifdef HASH_STATISTICS */ if ((hashp->cbucket < 0) || (flag == R_FIRST)) { hashp->cbucket = 0; hashp->cndx = 1; hashp->cpage = NULL; } next_bucket: for (bp = NULL; !bp || !bp[0]; ) { if (!(bufp = hashp->cpage)) { for (bucket = hashp->cbucket; bucket <= hashp->MAX_BUCKET; bucket++, hashp->cndx = 1) { bufp = __get_buf(hashp, bucket, NULL, 0); if (!bufp) return (ERROR); hashp->cpage = bufp; bp = (u_int16_t *)bufp->page; if (bp[0]) break; } hashp->cbucket = bucket; if (hashp->cbucket > hashp->MAX_BUCKET) { hashp->cbucket = -1; return (ABNORMAL); } } else { bp = (u_int16_t *)hashp->cpage->page; if (flag == R_NEXT) { hashp->cndx += 2; if (hashp->cndx > bp[0]) { hashp->cpage = NULL; hashp->cbucket++; hashp->cndx = 1; goto next_bucket; } } } #ifdef DEBUG assert(bp); assert(bufp); #endif /* ifdef DEBUG */ while (bp[hashp->cndx + 1] == OVFLPAGE) { bufp = hashp->cpage = __get_buf(hashp, bp[hashp->cndx], bufp, 0); if (!bufp) return (ERROR); bp = (u_int16_t *)(bufp->page); hashp->cndx = 1; } if (!bp[0]) { hashp->cpage = NULL; ++hashp->cbucket; } } ndx = hashp->cndx; if (bp[ndx + 1] < REAL_KEY) { if (__big_keydata(hashp, bufp, key, data, 1)) return (ERROR); } else { if (hashp->cpage == 0) return (ERROR); key->data = (unsigned char *)hashp->cpage->page + bp[ndx]; key->size = (ndx > 1 ? bp[ndx - 1] : hashp->BSIZE) - bp[ndx]; data->data = (unsigned char *)hashp->cpage->page + bp[ndx + 1]; data->size = bp[ndx] - bp[ndx + 1]; } return (SUCCESS); } /********************************* UTILITIES ************************/ /* * Returns: * 0 ==> OK * -1 ==> Error */ int __expand_table(HTAB *hashp) { u_int32_t old_bucket, new_bucket; int dirsize, new_segnum, spare_ndx; #ifdef HASH_STATISTICS hash_expansions++; #endif /* ifdef HASH_STATISTICS */ new_bucket = ++hashp->MAX_BUCKET; old_bucket = (hashp->MAX_BUCKET & hashp->LOW_MASK); new_segnum = new_bucket >> hashp->SSHIFT; /* Check if we need a new segment */ if (new_segnum >= hashp->nsegs) { /* Check if we need to expand directory */ if (new_segnum >= hashp->DSIZE) { /* Reallocate directory */ dirsize = hashp->DSIZE * sizeof(SEGMENT *); if (!hash_realloc(&hashp->dir, dirsize, dirsize << 1)) return (-1); hashp->DSIZE = dirsize << 1; } if ((hashp->dir[new_segnum] = (SEGMENT)calloc(hashp->SGSIZE, sizeof(SEGMENT))) == NULL) return (-1); hashp->exsegs++; hashp->nsegs++; } /* * If the split point is increasing (MAX_BUCKET's log base 2 * * increases), we need to copy the current contents of the spare * split bucket to the next bucket. */ spare_ndx = __log2(hashp->MAX_BUCKET + 1); if (spare_ndx > hashp->OVFL_POINT) { hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT]; hashp->OVFL_POINT = spare_ndx; } if (new_bucket > hashp->HIGH_MASK) { /* Starting a new doubling */ hashp->LOW_MASK = hashp->HIGH_MASK; hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK; } /* Relocate records to the new bucket */ return (__split_page(hashp, old_bucket, new_bucket)); } /* * If realloc guarantees that the pointer is not destroyed if the realloc * fails, then this routine can go away. */ static void * hash_realloc(SEGMENT **p_ptr, int oldsize, int newsize) { void *p; if ((p = malloc(newsize))) { memmove(p, *p_ptr, oldsize); memset((char *)p + oldsize, 0, newsize - oldsize); free(*p_ptr); *p_ptr = p; } return (p); } u_int32_t __call_hash(HTAB *hashp, char *k, int len) { int n, bucket; n = hashp->hash(k, len); bucket = n & hashp->HIGH_MASK; if (bucket > hashp->MAX_BUCKET) bucket = bucket & hashp->LOW_MASK; return (bucket); } /* * Allocate segment table. On error, destroy the table and set errno. * * Returns 0 on success */ static int alloc_segs(HTAB *hashp, int nsegs) { int i; SEGMENT store; int save_errno; if ((hashp->dir = (SEGMENT *)calloc(hashp->DSIZE, sizeof(SEGMENT *))) == NULL) { save_errno = errno; (void)hdestroy(hashp); errno = save_errno; return (-1); } hashp->nsegs = nsegs; if (nsegs == 0) return (0); /* Allocate segments */ if ((store = (SEGMENT)calloc(nsegs << hashp->SSHIFT, sizeof(SEGMENT))) == NULL) { save_errno = errno; (void)hdestroy(hashp); errno = save_errno; return (-1); } for (i = 0; i < nsegs; i++) hashp->dir[i] = &store[i << hashp->SSHIFT]; return (0); } #if BYTE_ORDER == LITTLE_ENDIAN /* * Hashp->hdr needs to be byteswapped. */ static void swap_header_copy(HASHHDR *srcp, HASHHDR *destp) { int i; P_32_COPY(srcp->magic, destp->magic); P_32_COPY(srcp->version, destp->version); P_32_COPY(srcp->lorder, destp->lorder); P_32_COPY(srcp->bsize, destp->bsize); P_32_COPY(srcp->bshift, destp->bshift); P_32_COPY(srcp->dsize, destp->dsize); P_32_COPY(srcp->ssize, destp->ssize); P_32_COPY(srcp->sshift, destp->sshift); P_32_COPY(srcp->ovfl_point, destp->ovfl_point); P_32_COPY(srcp->last_freed, destp->last_freed); P_32_COPY(srcp->max_bucket, destp->max_bucket); P_32_COPY(srcp->high_mask, destp->high_mask); P_32_COPY(srcp->low_mask, destp->low_mask); P_32_COPY(srcp->ffactor, destp->ffactor); P_32_COPY(srcp->nkeys, destp->nkeys); P_32_COPY(srcp->hdrpages, destp->hdrpages); P_32_COPY(srcp->h_charkey, destp->h_charkey); for (i = 0; i < NCACHED; i++) { P_32_COPY(srcp->spares[i], destp->spares[i]); P_16_COPY(srcp->bitmaps[i], destp->bitmaps[i]); } } static void swap_header(HTAB *hashp) { HASHHDR *hdrp; int i; hdrp = &hashp->hdr; M_32_SWAP(hdrp->magic); M_32_SWAP(hdrp->version); M_32_SWAP(hdrp->lorder); M_32_SWAP(hdrp->bsize); M_32_SWAP(hdrp->bshift); M_32_SWAP(hdrp->dsize); M_32_SWAP(hdrp->ssize); M_32_SWAP(hdrp->sshift); M_32_SWAP(hdrp->ovfl_point); M_32_SWAP(hdrp->last_freed); M_32_SWAP(hdrp->max_bucket); M_32_SWAP(hdrp->high_mask); M_32_SWAP(hdrp->low_mask); M_32_SWAP(hdrp->ffactor); M_32_SWAP(hdrp->nkeys); M_32_SWAP(hdrp->hdrpages); M_32_SWAP(hdrp->h_charkey); for (i = 0; i < NCACHED; i++) { M_32_SWAP(hdrp->spares[i]); M_16_SWAP(hdrp->bitmaps[i]); } } #endif /* if BYTE_ORDER == LITTLE_ENDIAN */ ================================================ FILE: db/hash/hash.h ================================================ /* $OpenBSD: hash.h,v 1.9 2004/06/21 23:13:22 marc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. * * @(#)hash.h 8.3 (Berkeley) 5/31/94 */ /* Operations */ typedef enum { HASH_GET, HASH_PUT, HASH_PUTNEW, HASH_DELETE, HASH_FIRST, HASH_NEXT } ACTION; /* Buffer Management structures */ typedef struct _bufhead BUFHEAD; struct _bufhead { BUFHEAD *prev; /* LRU links */ BUFHEAD *next; /* LRU links */ BUFHEAD *ovfl; /* Overflow page buffer header */ u_int32_t addr; /* Address of this page */ char *page; /* Actual page data */ char flags; #define BUF_MOD 0x0001 #define BUF_DISK 0x0002 #define BUF_BUCKET 0x0004 #define BUF_PIN 0x0008 }; #define IS_BUCKET(X) ((X) & BUF_BUCKET) typedef BUFHEAD **SEGMENT; /* Hash Table Information */ typedef struct hashhdr { /* Disk resident portion */ int32_t magic; /* Magic NO for hash tables */ int32_t version; /* Version ID */ u_int32_t lorder; /* Byte Order */ int32_t bsize; /* Bucket/Page Size */ int32_t bshift; /* Bucket shift */ int32_t dsize; /* Directory Size */ int32_t ssize; /* Segment Size */ int32_t sshift; /* Segment shift */ int32_t ovfl_point; /* Where overflow pages are being * allocated */ int32_t last_freed; /* Last overflow page freed */ int32_t max_bucket; /* ID of Maximum bucket in use */ int32_t high_mask; /* Mask to modulo into entire table */ int32_t low_mask; /* Mask to modulo into lower half of * table */ int32_t ffactor; /* Fill factor */ int32_t nkeys; /* Number of keys in hash table */ int32_t hdrpages; /* Size of table header */ int32_t h_charkey; /* value of hash(CHARKEY) */ #define NCACHED 32 /* number of bit maps and spare * points */ int32_t spares[NCACHED];/* spare pages for overflow */ u_int16_t bitmaps[NCACHED]; /* address of overflow page * bitmaps */ } HASHHDR; typedef struct htab { /* Memory resident data structure */ HASHHDR hdr; /* Header */ int nsegs; /* Number of allocated segments */ int exsegs; /* Number of extra allocated * segments */ u_int32_t /* Hash function */ (*hash)(const void *, size_t); int flags; /* Flag values */ int fp; /* File pointer */ char *tmp_buf; /* Temporary Buffer for BIG data */ char *tmp_key; /* Temporary Buffer for BIG keys */ BUFHEAD *cpage; /* Current page */ int cbucket; /* Current bucket */ int cndx; /* Index of next item on cpage */ int err; /* Error Number -- for DBM * compatibility */ int new_file; /* Indicates if fd is backing store * or no */ int save_file; /* Indicates whether we need to flush * file at * exit */ u_int32_t *mapp[NCACHED]; /* Pointers to page maps */ int nmaps; /* Initial number of bitmaps */ int nbufs; /* Number of buffers left to * allocate */ BUFHEAD bufhead; /* Header of buffer lru list */ SEGMENT *dir; /* Hash Bucket directory */ } HTAB; /* * Constants */ #define MAX_BSIZE 65536 /* 2^16 */ #define MIN_BUFFERS 6 #define MINHDRSIZE 512 #define DEF_BUFSIZE 65536 /* 64 K */ #define DEF_BUCKET_SIZE 4096 #define DEF_BUCKET_SHIFT 12 /* log2(BUCKET) */ #define DEF_SEGSIZE 256 #define DEF_SEGSIZE_SHIFT 8 /* log2(SEGSIZE) */ #define DEF_DIRSIZE 256 #define DEF_FFACTOR 65536 #define MIN_FFACTOR 4 #define SPLTMAX 8 #define CHARKEY "%$sniglet^&" #define NUMKEY 1038583 #define BYTE_SHIFT 3 #define INT_TO_BYTE 2 #define INT_BYTE_SHIFT 5 #define ALL_SET ((u_int32_t)0xFFFFFFFF) #define ALL_CLEAR 0 #define PTROF(X) ((BUFHEAD *)((ptrdiff_t)(X)&~0x3)) #define ISMOD(X) ((u_int32_t)(ptrdiff_t)(X)&0x1) #define DOMOD(X) ((X) = (char *)((ptrdiff_t)(X)|0x1)) #define ISDISK(X) ((u_int32_t)(ptrdiff_t)(X)&0x2) #define DODISK(X) ((X) = (char *)((ptrdiff_t)(X)|0x2)) #define BITS_PER_MAP 32 /* Given the address of the beginning of a big map, clear/set the nth bit */ #define CLRBIT(A, N) ((A)[(N)/BITS_PER_MAP] &= ~(1U<<((N)%BITS_PER_MAP))) #define SETBIT(A, N) ((A)[(N)/BITS_PER_MAP] |= (1U<<((N)%BITS_PER_MAP))) #define ISSET(A, N) ((A)[(N)/BITS_PER_MAP] & (1U<<((N)%BITS_PER_MAP))) /* Overflow management */ /* * Overflow page numbers are allocated per split point. At each doubling of * the table, we can allocate extra pages. So, an overflow page number has * the top 5 bits indicate which split point and the lower 11 bits indicate * which page at that split point is indicated (pages within split points are * numberered starting with 1). */ #define SPLITSHIFT 11 #define SPLITMASK 0x7FF #define SPLITNUM(N) (((u_int32_t)(N)) >> SPLITSHIFT) #define OPAGENUM(N) ((N) & SPLITMASK) #define OADDR_OF(S,O) ((u_int32_t)((u_int32_t)(S) << SPLITSHIFT) + (O)) #define BUCKET_TO_PAGE(B) \ (B) + hashp->HDRPAGES + ((B) ? hashp->SPARES[__log2((B)+1)-1] : 0) #define OADDR_TO_PAGE(B) \ BUCKET_TO_PAGE ( (1 << SPLITNUM((B))) -1 ) + OPAGENUM((B)); /* * page.h contains a detailed description of the page format. * * Normally, keys and data are accessed from offset tables in the top of * each page which point to the beginning of the key and data. There are * four flag values which may be stored in these offset tables which indicate * the following: * * * OVFLPAGE Rather than a key data pair, this pair contains * the address of an overflow page. The format of * the pair is: * OVERFLOW_PAGE_NUMBER OVFLPAGE * * PARTIAL_KEY This must be the first key/data pair on a page * and implies that page contains only a partial key. * That is, the key is too big to fit on a single page * so it starts on this page and continues on the next. * The format of the page is: * KEY_OFF PARTIAL_KEY OVFL_PAGENO OVFLPAGE * * KEY_OFF -- offset of the beginning of the key * PARTIAL_KEY -- 1 * OVFL_PAGENO - page number of the next overflow page * OVFLPAGE -- 0 * * FULL_KEY This must be the first key/data pair on the page. It * is used in two cases. * * Case 1: * There is a complete key on the page but no data * (because it wouldn't fit). The next page contains * the data. * * Page format it: * KEY_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE * * KEY_OFF -- offset of the beginning of the key * FULL_KEY -- 2 * OVFL_PAGENO - page number of the next overflow page * OVFLPAGE -- 0 * * Case 2: * This page contains no key, but part of a large * data field, which is continued on the next page. * * Page format it: * DATA_OFF FULL_KEY OVFL_PAGENO OVFL_PAGE * * KEY_OFF -- offset of the beginning of the data on * this page * FULL_KEY -- 2 * OVFL_PAGENO - page number of the next overflow page * OVFLPAGE -- 0 * * FULL_KEY_DATA * This must be the first key/data pair on the page. * There are two cases: * * Case 1: * This page contains a key and the beginning of the * data field, but the data field is continued on the * next page. * * Page format is: * KEY_OFF FULL_KEY_DATA OVFL_PAGENO DATA_OFF * * KEY_OFF -- offset of the beginning of the key * FULL_KEY_DATA -- 3 * OVFL_PAGENO - page number of the next overflow page * DATA_OFF -- offset of the beginning of the data * * Case 2: * This page contains the last page of a big data pair. * There is no key, only the tail end of the data * on this page. * * Page format is: * DATA_OFF FULL_KEY_DATA * * DATA_OFF -- offset of the beginning of the data on * this page * FULL_KEY_DATA -- 3 * OVFL_PAGENO - page number of the next overflow page * OVFLPAGE -- 0 * * OVFL_PAGENO and OVFLPAGE are optional (they are * not present if there is no next page). */ #define OVFLPAGE 0 #define PARTIAL_KEY 1 #define FULL_KEY 2 #define FULL_KEY_DATA 3 #define REAL_KEY 4 /* Short hands for accessing structure */ #define BSIZE hdr.bsize #define BSHIFT hdr.bshift #define DSIZE hdr.dsize #define SGSIZE hdr.ssize #define SSHIFT hdr.sshift #define LORDER hdr.lorder #define OVFL_POINT hdr.ovfl_point #define LAST_FREED hdr.last_freed #define MAX_BUCKET hdr.max_bucket #define FFACTOR hdr.ffactor #define HIGH_MASK hdr.high_mask #define LOW_MASK hdr.low_mask #define NKEYS hdr.nkeys #define HDRPAGES hdr.hdrpages #define SPARES hdr.spares #define BITMAPS hdr.bitmaps #define VERSION hdr.version #define MAGIC hdr.magic #define NEXT_FREE hdr.next_free #define H_CHARKEY hdr.h_charkey ================================================ FILE: db/hash/hash_bigkey.c ================================================ /* $OpenBSD: hash_bigkey.c,v 1.19 2015/12/28 22:08:18 mmcc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ /* * PACKAGE: hash * DESCRIPTION: * Big key/data handling for the hashing package. * * ROUTINES: * External * __big_keydata * __big_split * __big_insert * __big_return * __big_delete * __find_last_page * Internal * collect_key * collect_data */ #include "../../include/compat.h" #include #include #include #include #ifdef DEBUG # include #endif /* ifdef DEBUG */ #include #include #include "hash.h" #include "page.h" #include "extern.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) static int collect_key(HTAB *, BUFHEAD *, int, DBT *, int); static int collect_data(HTAB *, BUFHEAD *, int, int); /* * Big_insert * * You need to do an insert and the key/data pair is too big * * Returns: * 0 ==> OK *-1 ==> ERROR */ int __big_insert(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) { u_int16_t *p; int key_size, n, val_size; u_int16_t space, move_bytes, off; char *cp, *key_data, *val_data; cp = bufp->page; /* Character pointer of p. */ p = (u_int16_t *)cp; key_data = (char *)key->data; key_size = key->size; val_data = (char *)val->data; val_size = val->size; /* First move the Key */ for (space = FREESPACE(p) - BIGOVERHEAD; key_size; space = FREESPACE(p) - BIGOVERHEAD) { move_bytes = MINIMUM(space, key_size); off = OFFSET(p) - move_bytes; memmove(cp + off, key_data, move_bytes); key_size -= move_bytes; key_data += move_bytes; n = p[0]; p[++n] = off; p[0] = ++n; FREESPACE(p) = off - PAGE_META(n); OFFSET(p) = off; p[n] = PARTIAL_KEY; bufp = __add_ovflpage(hashp, bufp); if (!bufp) return (-1); n = p[0]; if (!key_size) { space = FREESPACE(p); if (space) { move_bytes = MINIMUM(space, val_size); /* * If the data would fit exactly in the * remaining space, we must overflow it to the * next page; otherwise the invariant that the * data must end on a page with FREESPACE * non-zero would fail. */ if (space == val_size && val_size == val->size) goto toolarge; off = OFFSET(p) - move_bytes; memmove(cp + off, val_data, move_bytes); val_data += move_bytes; val_size -= move_bytes; p[n] = off; p[n - 2] = FULL_KEY_DATA; FREESPACE(p) = FREESPACE(p) - move_bytes; OFFSET(p) = off; } else { toolarge: p[n - 2] = FULL_KEY; } } p = (u_int16_t *)bufp->page; cp = bufp->page; bufp->flags |= BUF_MOD; } /* Now move the data */ for (space = FREESPACE(p) - BIGOVERHEAD; val_size; space = FREESPACE(p) - BIGOVERHEAD) { move_bytes = MINIMUM(space, val_size); /* * Here's the hack to make sure that if the data ends on the * same page as the key ends, FREESPACE is at least one. */ if (space == val_size && val_size == val->size) move_bytes--; off = OFFSET(p) - move_bytes; memmove(cp + off, val_data, move_bytes); val_size -= move_bytes; val_data += move_bytes; n = p[0]; p[++n] = off; p[0] = ++n; FREESPACE(p) = off - PAGE_META(n); OFFSET(p) = off; if (val_size) { p[n] = FULL_KEY; bufp = __add_ovflpage(hashp, bufp); if (!bufp) return (-1); cp = bufp->page; p = (u_int16_t *)cp; } else p[n] = FULL_KEY_DATA; bufp->flags |= BUF_MOD; } return (0); } /* * Called when bufp's page contains a partial key (index should be 1) * * All pages in the big key/data pair except bufp are freed. We cannot * free bufp because the page pointing to it is lost and we can't get rid * of its pointer. * * Returns: * 0 => OK *-1 => ERROR */ int __big_delete(HTAB *hashp, BUFHEAD *bufp) { BUFHEAD *last_bfp, *rbufp; u_int16_t *bp, pageno; int key_done, n; rbufp = bufp; last_bfp = NULL; bp = (u_int16_t *)bufp->page; pageno = 0; (void)pageno; key_done = 0; while (!key_done || (bp[2] != FULL_KEY_DATA)) { if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA) key_done = 1; /* * If there is freespace left on a FULL_KEY_DATA page, then * the data is short and fits entirely on this page, and this * is the last page. */ if (bp[2] == FULL_KEY_DATA && FREESPACE(bp)) break; pageno = bp[bp[0] - 1]; rbufp->flags |= BUF_MOD; rbufp = __get_buf(hashp, pageno, rbufp, 0); if (last_bfp) __free_ovflpage(hashp, last_bfp); last_bfp = rbufp; if (!rbufp) return (-1); /* Error. */ bp = (u_int16_t *)rbufp->page; } /* * If we get here then rbufp points to the last page of the big * key/data pair. Bufp points to the first one -- it should now be * empty pointing to the next page after this pair. Can't free it * because we don't have the page pointing to it. */ /* This is information from the last page of the pair. */ n = bp[0]; pageno = bp[n - 1]; /* Now, bp is the first page of the pair. */ bp = (u_int16_t *)bufp->page; if (n > 2) { /* There is an overflow page. */ bp[1] = pageno; bp[2] = OVFLPAGE; bufp->ovfl = rbufp->ovfl; } else /* This is the last page. */ bufp->ovfl = NULL; n -= 2; bp[0] = n; FREESPACE(bp) = hashp->BSIZE - PAGE_META(n); OFFSET(bp) = hashp->BSIZE; bufp->flags |= BUF_MOD; if (rbufp) __free_ovflpage(hashp, rbufp); if (last_bfp && last_bfp != rbufp) __free_ovflpage(hashp, last_bfp); hashp->NKEYS--; return (0); } /* * Returns: * 0 = key not found * -1 = get next overflow page * -2 means key not found and this is big key/data * -3 error */ int __find_bigpair(HTAB *hashp, BUFHEAD *bufp, int ndx, char *key, int size) { u_int16_t *bp; char *p; int ksize; u_int16_t bytes; char *kkey; bp = (u_int16_t *)bufp->page; p = bufp->page; ksize = size; kkey = key; for (bytes = hashp->BSIZE - bp[ndx]; bytes <= size && bp[ndx + 1] == PARTIAL_KEY; bytes = hashp->BSIZE - bp[ndx]) { if (memcmp(p + bp[ndx], kkey, bytes)) return (-2); kkey += bytes; ksize -= bytes; bufp = __get_buf(hashp, bp[ndx + 2], bufp, 0); if (!bufp) return (-3); p = bufp->page; bp = (u_int16_t *)p; ndx = 1; } if (bytes != ksize || memcmp(p + bp[ndx], kkey, bytes)) { #ifdef HASH_STATISTICS ++hash_collisions; #endif /* ifdef HASH_STATISTICS */ return (-2); } else return (ndx); } /* * Given the buffer pointer of the first overflow page of a big pair, * find the end of the big pair * * This will set bpp to the buffer header of the last page of the big pair. * It will return the pageno of the overflow page following the last page * of the pair; 0 if there isn't any (i.e. big pair is the last key in the * bucket) */ u_int16_t __find_last_page(HTAB *hashp, BUFHEAD **bpp) { BUFHEAD *bufp; u_int16_t *bp, pageno; int n; bufp = *bpp; bp = (u_int16_t *)bufp->page; for (;;) { n = bp[0]; /* * This is the last page if: the tag is FULL_KEY_DATA and * either only 2 entries OVFLPAGE marker is explicit there * is freespace on the page. */ if (bp[2] == FULL_KEY_DATA && ((n == 2) || (bp[n] == OVFLPAGE) || (FREESPACE(bp)))) break; pageno = bp[n - 1]; bufp = __get_buf(hashp, pageno, bufp, 0); if (!bufp) return (0); /* Need to indicate an error! */ bp = (u_int16_t *)bufp->page; } *bpp = bufp; if (bp[0] > 2) return (bp[3]); else return (0); } /* * Return the data for the key/data pair that begins on this page at this * index (index should always be 1). */ int __big_return(HTAB *hashp, BUFHEAD *bufp, int ndx, DBT *val, int set_current) { BUFHEAD *save_p; u_int16_t *bp, len, off, save_addr; char *tp; bp = (u_int16_t *)bufp->page; while (bp[ndx + 1] == PARTIAL_KEY) { bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (u_int16_t *)bufp->page; ndx = 1; } if (bp[ndx + 1] == FULL_KEY) { bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (u_int16_t *)bufp->page; save_p = bufp; save_addr = save_p->addr; off = bp[1]; len = 0; } else if (!FREESPACE(bp)) { /* * This is a hack. We can't distinguish between * FULL_KEY_DATA that contains complete data or * incomplete data, so we require that if the data * is complete, there is at least 1 byte of free * space left. */ off = bp[bp[0]]; len = bp[1] - off; save_p = bufp; save_addr = bufp->addr; bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (u_int16_t *)bufp->page; (void)bp; } else { /* The data is all on one page. */ tp = (char *)bp; off = bp[bp[0]]; val->data = (unsigned char *)tp + off; val->size = bp[1] - off; if (set_current) { if (bp[0] == 2) { /* No more buckets in * chain */ hashp->cpage = NULL; hashp->cbucket++; hashp->cndx = 1; } else { hashp->cpage = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!hashp->cpage) return (-1); hashp->cndx = 1; if (!((u_int16_t *) hashp->cpage->page)[0]) { hashp->cbucket++; hashp->cpage = NULL; } } } return (0); } val->size = (size_t)collect_data(hashp, bufp, (int)len, set_current); if (val->size == (size_t)-1) return (-1); if (save_p->addr != save_addr) { /* We are pretty short on buffers. */ errno = EINVAL; /* OUT OF BUFFERS */ return (-1); } memmove(hashp->tmp_buf, (save_p->page) + off, len); val->data = (unsigned char *)hashp->tmp_buf; return (0); } /* * Count how big the total datasize is by recursing through the pages. Then * allocate a buffer and copy the data as you recurse up. */ static int collect_data(HTAB *hashp, BUFHEAD *bufp, int len, int set) { u_int16_t *bp; char *p; BUFHEAD *xbp; u_int16_t save_addr; int mylen, totlen; p = bufp->page; bp = (u_int16_t *)p; mylen = hashp->BSIZE - bp[1]; save_addr = bufp->addr; if (bp[2] == FULL_KEY_DATA) { /* End of Data */ totlen = len + mylen; free(hashp->tmp_buf); if ((hashp->tmp_buf = (char *)malloc(totlen)) == NULL) return (-1); if (set) { hashp->cndx = 1; if (bp[0] == 2) { /* No more buckets in chain */ hashp->cpage = NULL; hashp->cbucket++; } else { hashp->cpage = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!hashp->cpage) return (-1); else if (!((u_int16_t *)hashp->cpage->page)[0]) { hashp->cbucket++; hashp->cpage = NULL; } } } } else { xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!xbp || ((totlen = collect_data(hashp, xbp, len + mylen, set)) < 1)) return (-1); } if (bufp->addr != save_addr) { errno = EINVAL; /* Out of buffers. */ return (-1); } memmove(&hashp->tmp_buf[len], (bufp->page) + bp[1], mylen); return (totlen); } /* * Fill in the key and data for this big pair. */ int __big_keydata(HTAB *hashp, BUFHEAD *bufp, DBT *key, DBT *val, int set) { key->size = (size_t)collect_key(hashp, bufp, 0, val, set); if (key->size == (size_t)-1) return (-1); key->data = (unsigned char *)hashp->tmp_key; return (0); } /* * Count how big the total key size is by recursing through the pages. Then * collect the data, allocate a buffer and copy the key as you recurse up. */ static int collect_key(HTAB *hashp, BUFHEAD *bufp, int len, DBT *val, int set) { BUFHEAD *xbp; char *p; int mylen, totlen; u_int16_t *bp, save_addr; p = bufp->page; bp = (u_int16_t *)p; mylen = hashp->BSIZE - bp[1]; save_addr = bufp->addr; totlen = len + mylen; if (bp[2] == FULL_KEY || bp[2] == FULL_KEY_DATA) { /* End of Key. */ free(hashp->tmp_key); if ((hashp->tmp_key = (char *)malloc(totlen)) == NULL) return (-1); if (__big_return(hashp, bufp, 1, val, set)) return (-1); } else { xbp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!xbp || ((totlen = collect_key(hashp, xbp, totlen, val, set)) < 1)) return (-1); } if (bufp->addr != save_addr) { errno = EINVAL; /* MIS -- OUT OF BUFFERS */ return (-1); } memmove(&hashp->tmp_key[len], (bufp->page) + bp[1], mylen); return (totlen); } /* * Returns: * 0 => OK * -1 => error */ int __big_split(HTAB *hashp, BUFHEAD *op, /* Pointer to where to put keys that go in old bucket */ BUFHEAD *np, /* Pointer to new bucket page */ BUFHEAD *big_keyp, /* Pointer to first page containing the big key/data */ int addr, /* Address of big_keyp */ u_int32_t obucket, /* Old Bucket */ SPLIT_RETURN *ret) { BUFHEAD *bp, *tmpp; DBT key, val; u_int32_t change; u_int16_t free_space, n, off, *tp; bp = big_keyp; /* Now figure out where the big key/data goes */ if (__big_keydata(hashp, big_keyp, &key, &val, 0)) return (-1); change = (__call_hash(hashp, key.data, key.size) != obucket); if ((ret->next_addr = __find_last_page(hashp, &big_keyp))) { if (!(ret->nextp = __get_buf(hashp, ret->next_addr, big_keyp, 0))) return (-1); } else ret->nextp = NULL; /* Now make one of np/op point to the big key/data pair */ #ifdef DEBUG assert(np->ovfl == NULL); #endif /* ifdef DEBUG */ if (change) tmpp = np; else tmpp = op; tmpp->flags |= BUF_MOD; #ifdef DEBUG1 (void)fprintf(stderr, "BIG_SPLIT: %d->ovfl was %d is now %d\n", tmpp->addr, (tmpp->ovfl ? tmpp->ovfl->addr : 0), (bp ? bp->addr : 0)); #endif /* ifdef DEBUG1 */ tmpp->ovfl = bp; /* one of op/np point to big_keyp */ tp = (u_int16_t *)tmpp->page; #ifdef DEBUG assert(FREESPACE(tp) >= OVFLSIZE); #endif /* ifdef DEBUG */ n = tp[0]; off = OFFSET(tp); free_space = FREESPACE(tp); tp[++n] = (u_int16_t)addr; tp[++n] = OVFLPAGE; tp[0] = n; OFFSET(tp) = off; FREESPACE(tp) = free_space - OVFLSIZE; /* * Finally, set the new and old return values. BIG_KEYP contains a * pointer to the last page of the big key_data pair. Make sure that * big_keyp has no following page (2 elements) or create an empty * following page. */ ret->newp = np; ret->oldp = op; tp = (u_int16_t *)big_keyp->page; big_keyp->flags |= BUF_MOD; if (tp[0] > 2) { /* * There may be either one or two offsets on this page. If * there is one, then the overflow page is linked on normally * and tp[4] is OVFLPAGE. If there are two, tp[4] contains * the second offset and needs to get stuffed in after the * next overflow page is added. */ n = tp[4]; free_space = FREESPACE(tp); off = OFFSET(tp); tp[0] -= 2; FREESPACE(tp) = free_space + OVFLSIZE; OFFSET(tp) = off; tmpp = __add_ovflpage(hashp, big_keyp); if (!tmpp) return (-1); tp[4] = n; } else tmpp = big_keyp; if (change) ret->newp = tmpp; else ret->oldp = tmpp; return (0); } ================================================ FILE: db/hash/hash_buf.c ================================================ /* $OpenBSD: hash_buf.c,v 1.19 2015/01/16 16:48:51 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ /* * PACKAGE: hash * * DESCRIPTION: * Contains buffer management * * ROUTINES: * External * __buf_init * __get_buf * __buf_free * __reclaim_buf * Internal * newbuf */ #include "../../include/compat.h" #include #include #include #include #include #ifdef DEBUG # include #endif /* ifdef DEBUG */ #include #include #include "hash.h" #include "page.h" #include "extern.h" #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) static BUFHEAD *newbuf(HTAB *, u_int32_t, BUFHEAD *); /* Unlink B from its place in the lru */ #define BUF_REMOVE(B) { \ (B)->prev->next = (B)->next; \ (B)->next->prev = (B)->prev; \ } /* Insert B after P */ #define BUF_INSERT(B, P) { \ (B)->next = (P)->next; \ (B)->prev = (P); \ (P)->next = (B); \ (B)->next->prev = (B); \ } #define MRU hashp->bufhead.next #define LRU hashp->bufhead.prev #define MRU_INSERT(B) BUF_INSERT((B), &hashp->bufhead) #define LRU_INSERT(B) BUF_INSERT((B), LRU) /* * We are looking for a buffer with address "addr". If prev_bp is NULL, then * address is a bucket index. If prev_bp is not NULL, then it points to the * page previous to an overflow page that we are trying to find. * * CAVEAT: The buffer header accessed via prev_bp's ovfl field may no longer * be valid. Therefore, you must always verify that its address matches the * address you are seeking. */ BUFHEAD * __get_buf(HTAB *hashp, u_int32_t addr, BUFHEAD *prev_bp, /* If prev_bp set, indicates a new overflow page. */ int newpage) { BUFHEAD *bp; u_int32_t is_disk_mask; int is_disk, segment_ndx; SEGMENT segp; is_disk = 0; is_disk_mask = 0; if (prev_bp) { bp = prev_bp->ovfl; if (!bp || (bp->addr != addr)) bp = NULL; if (!newpage) is_disk = BUF_DISK; } else { /* Grab buffer out of directory */ segment_ndx = addr & (hashp->SGSIZE - 1); /* valid segment ensured by __call_hash() */ segp = hashp->dir[addr >> hashp->SSHIFT]; #ifdef DEBUG assert(segp != NULL); #endif /* ifdef DEBUG */ bp = PTROF(segp[segment_ndx]); is_disk_mask = ISDISK(segp[segment_ndx]); is_disk = is_disk_mask || !hashp->new_file; } if (!bp) { bp = newbuf(hashp, addr, prev_bp); if (!bp || __get_page(hashp, bp->page, addr, !prev_bp, is_disk, 0)) return (NULL); if (!prev_bp) segp[segment_ndx] = (BUFHEAD *)((ptrdiff_t)bp | is_disk_mask); } else { BUF_REMOVE(bp); MRU_INSERT(bp); } return (bp); } /* * We need a buffer for this page. Either allocate one, or evict a resident * one (if we have as many buffers as we're allowed) and put this one in. * * If newbuf finds an error (returning NULL), it also sets errno. */ static BUFHEAD * newbuf(HTAB *hashp, u_int32_t addr, BUFHEAD *prev_bp) { BUFHEAD *bp; /* The buffer we're going to use */ BUFHEAD *xbp; /* Temp pointer */ BUFHEAD *next_xbp; SEGMENT segp; int segment_ndx; u_int16_t oaddr, *shortp; oaddr = 0; bp = LRU; /* It is bad to overwrite the page under the cursor. */ if (bp == hashp->cpage) { BUF_REMOVE(bp); MRU_INSERT(bp); bp = LRU; } /* If prev_bp is part of bp overflow, create a new buffer. */ if (hashp->nbufs == 0 && prev_bp && bp->ovfl) { BUFHEAD *ovfl; for (ovfl = bp->ovfl; ovfl ; ovfl = ovfl->ovfl) { if (ovfl == prev_bp) { hashp->nbufs++; break; } } } /* * If LRU buffer is pinned, the buffer pool is too small. We need to * allocate more buffers. */ if (hashp->nbufs || (bp->flags & BUF_PIN) || bp == hashp->cpage) { /* Allocate a new one */ if ((bp = (BUFHEAD *)malloc(sizeof(BUFHEAD))) == NULL) return (NULL); memset(bp, 0xff, sizeof(BUFHEAD)); if ((bp->page = (char *)malloc(hashp->BSIZE)) == NULL) { free(bp); return (NULL); } memset(bp->page, 0xff, hashp->BSIZE); if (hashp->nbufs) hashp->nbufs--; } else { /* Kick someone out */ BUF_REMOVE(bp); /* * If this is an overflow page with addr 0, it's already been * flushed back in an overflow chain and initialized. */ if ((bp->addr != 0) || (bp->flags & BUF_BUCKET)) { /* * Set oaddr before __put_page so that you get it * before bytes are swapped. */ shortp = (u_int16_t *)bp->page; if (shortp[0]) oaddr = shortp[shortp[0] - 1]; if ((bp->flags & BUF_MOD) && __put_page(hashp, bp->page, bp->addr, (int)IS_BUCKET(bp->flags), 0)) return (NULL); /* * Update the pointer to this page (i.e. invalidate it). * * If this is a new file (i.e. we created it at open * time), make sure that we mark pages which have been * written to disk so we retrieve them from disk later, * rather than allocating new pages. */ if (IS_BUCKET(bp->flags)) { segment_ndx = bp->addr & (hashp->SGSIZE - 1); segp = hashp->dir[bp->addr >> hashp->SSHIFT]; #ifdef DEBUG assert(segp != NULL); #endif /* ifdef DEBUG */ if (hashp->new_file && ((bp->flags & BUF_MOD) || ISDISK(segp[segment_ndx]))) segp[segment_ndx] = (BUFHEAD *)BUF_DISK; else segp[segment_ndx] = NULL; } /* * Since overflow pages can only be access by means of * their bucket, free overflow pages associated with * this bucket. */ for (xbp = bp; xbp->ovfl;) { next_xbp = xbp->ovfl; xbp->ovfl = 0; xbp = next_xbp; /* Check that ovfl pointer is up date. */ if (IS_BUCKET(xbp->flags) || (oaddr != xbp->addr)) break; shortp = (u_int16_t *)xbp->page; if (shortp[0]) /* set before __put_page */ oaddr = shortp[shortp[0] - 1]; if ((xbp->flags & BUF_MOD) && __put_page(hashp, xbp->page, xbp->addr, 0, 0)) return (NULL); xbp->addr = 0; xbp->flags = 0; BUF_REMOVE(xbp); LRU_INSERT(xbp); } } } /* Now assign this buffer */ bp->addr = addr; #ifdef DEBUG1 (void)fprintf(stderr, "NEWBUF1: %d->ovfl was %d is now %d\n", bp->addr, (bp->ovfl ? bp->ovfl->addr : 0), 0); #endif /* ifdef DEBUG1 */ bp->ovfl = NULL; if (prev_bp) { /* * If prev_bp is set, this is an overflow page, hook it in to * the buffer overflow links. */ #ifdef DEBUG1 (void)fprintf(stderr, "NEWBUF2: %d->ovfl was %d is now %d\n", prev_bp->addr, (prev_bp->ovfl ? prev_bp->ovfl->addr : 0), (bp ? bp->addr : 0)); #endif /* ifdef DEBUG1 */ prev_bp->ovfl = bp; bp->flags = 0; } else bp->flags = BUF_BUCKET; MRU_INSERT(bp); return (bp); } void __buf_init(HTAB *hashp, int nbytes) { BUFHEAD *bfp; int npages; bfp = &(hashp->bufhead); npages = (nbytes + hashp->BSIZE - 1) >> hashp->BSHIFT; npages = MAXIMUM(npages, MIN_BUFFERS); hashp->nbufs = npages; bfp->next = bfp; bfp->prev = bfp; /* * This space is calloc'd so these are already null. * * bfp->ovfl = NULL; * bfp->flags = 0; * bfp->page = NULL; * bfp->addr = 0; */ } int __buf_free(HTAB *hashp, int do_free, int to_disk) { BUFHEAD *bp; /* Need to make sure that buffer manager has been initialized */ if (!LRU) return (0); for (bp = LRU; bp != &hashp->bufhead;) { /* Check that the buffer is valid */ if (bp->addr || IS_BUCKET(bp->flags)) { if (to_disk && (bp->flags & BUF_MOD) && __put_page(hashp, bp->page, bp->addr, IS_BUCKET(bp->flags), 0)) return (-1); } /* Check if we are freeing stuff */ if (do_free) { if (bp->page) { (void)memset(bp->page, 0, hashp->BSIZE); free(bp->page); } BUF_REMOVE(bp); free(bp); bp = LRU; } else bp = bp->prev; } return (0); } void __reclaim_buf(HTAB *hashp, BUFHEAD *bp) { bp->ovfl = 0; bp->addr = 0; bp->flags = 0; BUF_REMOVE(bp); LRU_INSERT(bp); } ================================================ FILE: db/hash/hash_func.c ================================================ /* $OpenBSD: hash_func.c,v 1.11 2016/05/29 20:47:49 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ #include "../../include/compat.h" #include #include #include /* Chris Torek's hash function. */ u_int32_t __default_hash(const void *key, size_t len) { u_int32_t h, loop; u_int8_t *k; #define HASH4a h = (h << 5) - h + *k++; #define HASH4b h = (h << 5) + h + *k++; #define HASH4 HASH4b h = 0; k = (u_int8_t *)key; if (len > 0) { loop = (len + 8 - 1) >> 3; switch (len & (8 - 1)) { case 0: do { /* All fall throughs */ HASH4; /* FALLTHROUGH */ case 7: HASH4; /* FALLTHROUGH */ case 6: HASH4; /* FALLTHROUGH */ case 5: HASH4; /* FALLTHROUGH */ case 4: HASH4; /* FALLTHROUGH */ case 3: HASH4; /* FALLTHROUGH */ case 2: HASH4; /* FALLTHROUGH */ case 1: HASH4; /* FALLTHROUGH */ } while (--loop); } } return (h); } ================================================ FILE: db/hash/hash_log2.c ================================================ /* $OpenBSD: hash_log2.c,v 1.8 2005/08/05 13:03:00 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ #include "../../include/compat.h" #include #include #include "hash.h" #include "page.h" #include "extern.h" u_int32_t __log2(u_int32_t num) { u_int32_t i, limit; limit = 1; for (i = 0; limit < num; limit = limit << 1, i++); return (i); } ================================================ FILE: db/hash/hash_page.c ================================================ /* $OpenBSD: hash_page.c,v 1.23 2016/12/18 17:07:58 krw Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ /* * PACKAGE: hashing * * DESCRIPTION: * Page manipulation for hashing package. * * ROUTINES: * * External * __get_page * __add_ovflpage * Internal * overflow_page * open_temp */ #ifndef EFTYPE # define EFTYPE ENOTSUP #endif /* ifndef EFTYPE */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #ifdef DEBUG # include #endif /* ifdef DEBUG */ #include #include #include "hash.h" #include "page.h" #include "extern.h" #if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) # define BIG_ENDIAN 4321 # define LITTLE_ENDIAN 1234 #endif /* if ( !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) ) */ #if ( !defined(BYTE_ORDER) && defined(_AIX) ) # if ( defined(__powerpc__) || defined(__PPC__) \ || defined(_ARCH_PPC) ) # define BYTE_ORDER BIG_ENDIAN /* Assume AIX/PPC is big-endian */ # endif /* if ( defined(__powerpc__) || defined(__PPC__) || defined(_ARCH_PPC) ) */ #endif /* if ( !defined(BYTE_ORDER) && defined(_AIX) ) */ #undef open static u_int32_t *fetch_bitmap(HTAB *, int); static u_int32_t first_free(u_int32_t); static int open_temp(HTAB *); static u_int16_t overflow_page(HTAB *); static void putpair(char *, const DBT *, const DBT *); static void squeeze_key(u_int16_t *, const DBT *, const DBT *); static int ugly_split(HTAB *, u_int32_t, BUFHEAD *, BUFHEAD *, int, int); #define PAGE_INIT(P) { \ ((u_int16_t *)(P))[0] = 0; \ ((u_int16_t *)(P))[1] = hashp->BSIZE - 3 * sizeof(u_int16_t); \ ((u_int16_t *)(P))[2] = hashp->BSIZE; \ } /* * This is called AFTER we have verified that there is room on the page for * the pair (PAIRFITS has returned true) so we go right ahead and start moving * stuff on. */ static void putpair(char *p, const DBT *key, const DBT *val) { u_int16_t *bp, n, off; bp = (u_int16_t *)p; /* Enter the key first. */ n = bp[0]; off = OFFSET(bp) - key->size; memmove(p + off, key->data, key->size); bp[++n] = off; /* Now the data. */ off -= val->size; memmove(p + off, val->data, val->size); bp[++n] = off; /* Adjust page info. */ bp[0] = n; bp[n + 1] = off - ((n + 3) * sizeof(u_int16_t)); bp[n + 2] = off; } /* * Returns: * 0 OK * -1 error */ int __delpair(HTAB *hashp, BUFHEAD *bufp, int ndx) { u_int16_t *bp, newoff, pairlen; int n; bp = (u_int16_t *)bufp->page; n = bp[0]; if (bp[ndx + 1] < REAL_KEY) return (__big_delete(hashp, bufp)); if (ndx != 1) newoff = bp[ndx - 1]; else newoff = hashp->BSIZE; pairlen = newoff - bp[ndx + 1]; if (ndx != (n - 1)) { /* Hard Case -- need to shuffle keys */ int i; char *src = bufp->page + (int)OFFSET(bp); char *dst = src + (int)pairlen; memmove(dst, src, bp[ndx + 1] - OFFSET(bp)); /* Now adjust the pointers */ for (i = ndx + 2; i <= n; i += 2) { if (bp[i + 1] == OVFLPAGE) { bp[i - 2] = bp[i]; bp[i - 1] = bp[i + 1]; } else { bp[i - 2] = bp[i] + pairlen; bp[i - 1] = bp[i + 1] + pairlen; } } if (ndx == hashp->cndx) { /* * We just removed pair we were "pointing" to. * By moving back the cndx we ensure subsequent * hash_seq() calls won't skip over any entries. */ hashp->cndx -= 2; } } /* Finally adjust the page data */ bp[n] = OFFSET(bp) + pairlen; bp[n - 1] = bp[n + 1] + pairlen + 2 * sizeof(u_int16_t); bp[0] = n - 2; hashp->NKEYS--; bufp->flags |= BUF_MOD; return (0); } /* * Returns: * 0 ==> OK * -1 ==> Error */ int __split_page(HTAB *hashp, u_int32_t obucket, u_int32_t nbucket) { BUFHEAD *new_bufp, *old_bufp; u_int16_t *ino; char *np; DBT key, val; int n, ndx, retval; u_int16_t copyto, diff, off, moved; char *op; copyto = (u_int16_t)hashp->BSIZE; off = (u_int16_t)hashp->BSIZE; old_bufp = __get_buf(hashp, obucket, NULL, 0); if (old_bufp == NULL) return (-1); new_bufp = __get_buf(hashp, nbucket, NULL, 0); if (new_bufp == NULL) return (-1); old_bufp->flags |= (BUF_MOD | BUF_PIN); new_bufp->flags |= (BUF_MOD | BUF_PIN); ino = (u_int16_t *)(op = old_bufp->page); np = new_bufp->page; moved = 0; for (n = 1, ndx = 1; n < ino[0]; n += 2) { if (ino[n + 1] < REAL_KEY) { retval = ugly_split(hashp, obucket, old_bufp, new_bufp, (int)copyto, (int)moved); old_bufp->flags &= ~BUF_PIN; new_bufp->flags &= ~BUF_PIN; return (retval); } key.data = (unsigned char *)op + ino[n]; key.size = off - ino[n]; if (__call_hash(hashp, key.data, key.size) == obucket) { /* Don't switch page */ diff = copyto - off; if (diff) { copyto = ino[n + 1] + diff; memmove(op + copyto, op + ino[n + 1], off - ino[n + 1]); ino[ndx] = copyto + ino[n] - ino[n + 1]; ino[ndx + 1] = copyto; } else copyto = ino[n + 1]; ndx += 2; } else { /* Switch page */ val.data = (unsigned char *)op + ino[n + 1]; val.size = ino[n] - ino[n + 1]; putpair(np, &key, &val); moved += 2; } off = ino[n + 1]; } /* Now clean up the page */ ino[0] -= moved; FREESPACE(ino) = copyto - sizeof(u_int16_t) * (ino[0] + 3); OFFSET(ino) = copyto; #ifdef DEBUG3 (void)fprintf(stderr, "split %d/%d\n", ((u_int16_t *)np)[0] / 2, ((u_int16_t *)op)[0] / 2); #endif /* ifdef DEBUG3 */ /* unpin both pages */ old_bufp->flags &= ~BUF_PIN; new_bufp->flags &= ~BUF_PIN; return (0); } /* * Called when we encounter an overflow or big key/data page during split * handling. This is special cased since we have to begin checking whether * the key/data pairs fit on their respective pages and because we may need * overflow pages for both the old and new pages. * * The first page might be a page with regular key/data pairs in which case * we have a regular overflow condition and just need to go on to the next * page or it might be a big key/data pair in which case we need to fix the * big key/data pair. * * Returns: * 0 ==> success * -1 ==> failure */ static int ugly_split(HTAB *hashp, u_int32_t obucket, /* Same as __split_page. */ BUFHEAD *old_bufp, BUFHEAD *new_bufp, int copyto, /* First byte on page which contains key/data values. */ int moved) /* Number of pairs moved to new page. */ { BUFHEAD *bufp; /* Buffer header for ino */ u_int16_t *ino; /* Page keys come off of */ u_int16_t *np; /* New page */ u_int16_t *op; /* Page keys go on to if they aren't moving */ BUFHEAD *last_bfp; /* Last buf header OVFL needing to be freed */ DBT key, val; SPLIT_RETURN ret; u_int16_t n, off, ov_addr, scopyto; char *cino; /* Character value of ino */ bufp = old_bufp; ino = (u_int16_t *)old_bufp->page; np = (u_int16_t *)new_bufp->page; op = (u_int16_t *)old_bufp->page; last_bfp = NULL; scopyto = (u_int16_t)copyto; /* ANSI */ n = ino[0] - 1; while (n < ino[0]) { if (ino[2] < REAL_KEY && ino[2] != OVFLPAGE) { if (__big_split(hashp, old_bufp, new_bufp, bufp, bufp->addr, obucket, &ret)) return (-1); old_bufp = ret.oldp; if (!old_bufp) return (-1); op = (u_int16_t *)old_bufp->page; new_bufp = ret.newp; if (!new_bufp) return (-1); np = (u_int16_t *)new_bufp->page; bufp = ret.nextp; if (!bufp) return (0); cino = (char *)bufp->page; ino = (u_int16_t *)cino; last_bfp = ret.nextp; } else if (ino[n + 1] == OVFLPAGE) { ov_addr = ino[n]; /* * Fix up the old page -- the extra 2 are the fields * which contained the overflow information. */ ino[0] -= (moved + 2); FREESPACE(ino) = scopyto - sizeof(u_int16_t) * (ino[0] + 3); OFFSET(ino) = scopyto; bufp = __get_buf(hashp, ov_addr, bufp, 0); if (!bufp) return (-1); ino = (u_int16_t *)bufp->page; n = 1; (void)n; scopyto = hashp->BSIZE; moved = 0; if (last_bfp) __free_ovflpage(hashp, last_bfp); last_bfp = bufp; } /* Move regular sized pairs of there are any */ off = hashp->BSIZE; for (n = 1; (n < ino[0]) && (ino[n + 1] >= REAL_KEY); n += 2) { cino = (char *)ino; key.data = (unsigned char *)cino + ino[n]; key.size = off - ino[n]; val.data = (unsigned char *)cino + ino[n + 1]; val.size = ino[n] - ino[n + 1]; off = ino[n + 1]; if (__call_hash(hashp, key.data, key.size) == obucket) { /* Keep on old page */ if (PAIRFITS(op, (&key), (&val))) putpair((char *)op, &key, &val); else { old_bufp = __add_ovflpage(hashp, old_bufp); if (!old_bufp) return (-1); op = (u_int16_t *)old_bufp->page; putpair((char *)op, &key, &val); } old_bufp->flags |= BUF_MOD; } else { /* Move to new page */ if (PAIRFITS(np, (&key), (&val))) putpair((char *)np, &key, &val); else { new_bufp = __add_ovflpage(hashp, new_bufp); if (!new_bufp) return (-1); np = (u_int16_t *)new_bufp->page; putpair((char *)np, &key, &val); } new_bufp->flags |= BUF_MOD; } } } if (last_bfp) __free_ovflpage(hashp, last_bfp); return (0); } /* * Add the given pair to the page * * Returns: * 0 ==> OK * 1 ==> failure */ int __addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val) { u_int16_t *bp, *sop; int do_expand; bp = (u_int16_t *)bufp->page; do_expand = 0; while (bp[0] && (bp[2] < REAL_KEY || bp[bp[0]] < REAL_KEY)) /* Exception case */ if (bp[2] == FULL_KEY_DATA && bp[0] == 2) /* This is the last page of a big key/data pair and we need to add another page */ break; else if (bp[2] < REAL_KEY && bp[bp[0]] != OVFLPAGE) { bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (u_int16_t *)bufp->page; } else if (bp[bp[0]] != OVFLPAGE) { /* Short key/data pairs, no more pages */ break; } else { /* Try to squeeze key on this page */ if (bp[2] >= REAL_KEY && FREESPACE(bp) >= PAIRSIZE(key, val)) { squeeze_key(bp, key, val); goto stats; } else { bufp = __get_buf(hashp, bp[bp[0] - 1], bufp, 0); if (!bufp) return (-1); bp = (u_int16_t *)bufp->page; } } if (PAIRFITS(bp, key, val)) putpair(bufp->page, key, val); else { do_expand = 1; bufp = __add_ovflpage(hashp, bufp); if (!bufp) return (-1); sop = (u_int16_t *)bufp->page; if (PAIRFITS(sop, key, val)) putpair((char *)sop, key, val); else if (__big_insert(hashp, bufp, key, val)) return (-1); } stats: bufp->flags |= BUF_MOD; /* * If the average number of keys per bucket exceeds the fill factor, * expand the table. */ hashp->NKEYS++; if (do_expand || (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR)) return (__expand_table(hashp)); return (0); } /* * * Returns: * pointer on success * NULL on error */ BUFHEAD * __add_ovflpage(HTAB *hashp, BUFHEAD *bufp) { u_int16_t *sp, ndx, ovfl_num; #ifdef DEBUG1 int tmp1, tmp2; #endif /* ifdef DEBUG1 */ sp = (u_int16_t *)bufp->page; /* Check if we are dynamically determining the fill factor */ if (hashp->FFACTOR == DEF_FFACTOR) { hashp->FFACTOR = sp[0] >> 1; if (hashp->FFACTOR < MIN_FFACTOR) hashp->FFACTOR = MIN_FFACTOR; } bufp->flags |= BUF_MOD; ovfl_num = overflow_page(hashp); #ifdef DEBUG1 tmp1 = bufp->addr; tmp2 = bufp->ovfl ? bufp->ovfl->addr : 0; #endif /* ifdef DEBUG1 */ if (!ovfl_num || !(bufp->ovfl = __get_buf(hashp, ovfl_num, bufp, 1))) return (NULL); bufp->ovfl->flags |= BUF_MOD; #ifdef DEBUG1 (void)fprintf(stderr, "ADDOVFLPAGE: %d->ovfl was %d is now %d\n", tmp1, tmp2, bufp->ovfl->addr); #endif /* ifdef DEBUG1 */ ndx = sp[0]; /* * Since a pair is allocated on a page only if there's room to add * an overflow page, we know that the OVFL information will fit on * the page. */ sp[ndx + 4] = OFFSET(sp); sp[ndx + 3] = FREESPACE(sp) - OVFLSIZE; sp[ndx + 1] = ovfl_num; sp[ndx + 2] = OVFLPAGE; sp[0] = ndx + 2; #ifdef HASH_STATISTICS hash_overflows++; #endif /* ifdef HASH_STATISTICS */ return (bufp->ovfl); } /* * Returns: * 0 indicates SUCCESS * -1 indicates FAILURE */ int __get_page(HTAB *hashp, char *p, u_int32_t bucket, int is_bucket, int is_disk, int is_bitmap) { int fd, page, size, rsize; u_int16_t *bp; fd = hashp->fp; size = hashp->BSIZE; if ((fd == -1) || !is_disk) { PAGE_INIT(p); return (0); } if (is_bucket) page = BUCKET_TO_PAGE(bucket); else page = OADDR_TO_PAGE(bucket); if ((rsize = pread(fd, p, size, (off_t)page << hashp->BSHIFT)) == -1) return (-1); bp = (u_int16_t *)p; if (!rsize) bp[0] = 0; /* We hit the EOF, so initialize a new page */ else if (rsize != size) { errno = EFTYPE; return (-1); } if (!is_bitmap && !bp[0]) { PAGE_INIT(p); } else if (hashp->LORDER != BYTE_ORDER) { int i, max; if (is_bitmap) { max = hashp->BSIZE >> 2; /* divide by 4 */ for (i = 0; i < max; i++) M_32_SWAP(((int *)p)[i]); } else { M_16_SWAP(bp[0]); max = bp[0] + 2; for (i = 1; i <= max; i++) M_16_SWAP(bp[i]); } } return (0); } /* * Write page p to disk * * Returns: * 0 ==> OK * -1 ==>failure */ int __put_page(HTAB *hashp, char *p, u_int32_t bucket, int is_bucket, int is_bitmap) { int fd, page, size, wsize; size = hashp->BSIZE; if ((hashp->fp == -1) && open_temp(hashp)) return (-1); fd = hashp->fp; if (hashp->LORDER != BYTE_ORDER) { int i, max; if (is_bitmap) { max = hashp->BSIZE >> 2; /* divide by 4 */ for (i = 0; i < max; i++) M_32_SWAP(((int *)p)[i]); } else { max = ((u_int16_t *)p)[0] + 2; for (i = 0; i <= max; i++) M_16_SWAP(((u_int16_t *)p)[i]); } } if (is_bucket) page = BUCKET_TO_PAGE(bucket); else page = OADDR_TO_PAGE(bucket); if ((wsize = pwrite(fd, p, size, (off_t)page << hashp->BSHIFT)) == -1) /* Errno is set */ return (-1); if (wsize != size) { errno = EFTYPE; return (-1); } return (0); } #define BYTE_MASK ((1 << INT_BYTE_SHIFT) -1) /* * Initialize a new bitmap page. Bitmap pages are left in memory * once they are read in. */ int __ibitmap(HTAB *hashp, int pnum, int nbits, int ndx) { u_int32_t *ip; int clearbytes, clearints; if ((ip = (u_int32_t *)malloc(hashp->BSIZE)) == NULL) return (1); hashp->nmaps++; clearints = ((nbits - 1) >> INT_BYTE_SHIFT) + 1; clearbytes = clearints << INT_TO_BYTE; (void)memset((char *)ip, 0, clearbytes); (void)memset(((char *)ip) + clearbytes, 0xFF, hashp->BSIZE - clearbytes); ip[clearints - 1] = ALL_SET << (nbits & BYTE_MASK); SETBIT(ip, 0); hashp->BITMAPS[ndx] = (u_int16_t)pnum; hashp->mapp[ndx] = ip; return (0); } static u_int32_t first_free(u_int32_t map) { u_int32_t i, mask; mask = 0x1; for (i = 0; i < BITS_PER_MAP; i++) { if (!(mask & map)) return (i); mask = mask << 1; } return (i); } static u_int16_t overflow_page(HTAB *hashp) { u_int32_t *freep; int max_free, offset, splitnum; u_int16_t addr; int bit, first_page, free_bit, free_page, i, in_use_bits, j; #ifdef DEBUG2 int tmp1, tmp2; #endif /* ifdef DEBUG2 */ splitnum = hashp->OVFL_POINT; max_free = hashp->SPARES[splitnum]; free_page = (max_free - 1) >> (hashp->BSHIFT + BYTE_SHIFT); free_bit = (max_free - 1) & ((hashp->BSIZE << BYTE_SHIFT) - 1); /* Look through all the free maps to find the first free block */ first_page = hashp->LAST_FREED >>(hashp->BSHIFT + BYTE_SHIFT); for ( i = first_page; i <= free_page; i++ ) { if (!(freep = (u_int32_t *)hashp->mapp[i]) && !(freep = fetch_bitmap(hashp, i))) return (0); if (i == free_page) in_use_bits = free_bit; else in_use_bits = (hashp->BSIZE << BYTE_SHIFT) - 1; if (i == first_page) { bit = hashp->LAST_FREED & ((hashp->BSIZE << BYTE_SHIFT) - 1); j = bit / BITS_PER_MAP; bit = bit & ~(BITS_PER_MAP - 1); } else { bit = 0; j = 0; } for (; bit <= in_use_bits; j++, bit += BITS_PER_MAP) if (freep[j] != ALL_SET) goto found; } /* No Free Page Found */ hashp->LAST_FREED = hashp->SPARES[splitnum]; hashp->SPARES[splitnum]++; offset = hashp->SPARES[splitnum] - (splitnum ? hashp->SPARES[splitnum - 1] : 0); #define OVMSG "HASH: Out of overflow pages. Increase page size\n" if (offset > SPLITMASK) { if (++splitnum >= NCACHED) { (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); errno = EFBIG; return (0); } hashp->OVFL_POINT = splitnum; hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1]; hashp->SPARES[splitnum-1]--; offset = 1; } /* Check if we need to allocate a new bitmap page */ if (free_bit == (hashp->BSIZE << BYTE_SHIFT) - 1) { free_page++; if (free_page >= NCACHED) { (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); errno = EFBIG; return (0); } /* * This is tricky. The 1 indicates that you want the new page * allocated with 1 clear bit. Actually, you are going to * allocate 2 pages from this map. The first is going to be * the map page, the second is the overflow page we were * looking for. The init_bitmap routine automatically, sets * the first bit of itself to indicate that the bitmap itself * is in use. We would explicitly set the second bit, but * don't have to if we tell init_bitmap not to leave it clear * in the first place. */ if (__ibitmap(hashp, (int)OADDR_OF(splitnum, offset), 1, free_page)) return (0); hashp->SPARES[splitnum]++; #ifdef DEBUG2 free_bit = 2; #endif /* ifdef DEBUG2 */ offset++; if (offset > SPLITMASK) { if (++splitnum >= NCACHED) { (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); errno = EFBIG; return (0); } hashp->OVFL_POINT = splitnum; hashp->SPARES[splitnum] = hashp->SPARES[splitnum-1]; hashp->SPARES[splitnum-1]--; offset = 0; } } else { /* * Free_bit addresses the last used bit. Bump it to address * the first available bit. */ free_bit++; SETBIT(freep, free_bit); } /* Calculate address of the new overflow page */ addr = OADDR_OF(splitnum, offset); #ifdef DEBUG2 (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n", addr, free_bit, free_page); #endif /* ifdef DEBUG2 */ return (addr); found: bit = bit + first_free(freep[j]); SETBIT(freep, bit); #ifdef DEBUG2 tmp1 = bit; tmp2 = i; #endif /* ifdef DEBUG2 */ /* * Bits are addressed starting with 0, but overflow pages are addressed * beginning at 1. Bit is a bit addressnumber, so we need to increment * it to convert it to a page number. */ bit = 1 + bit + (i * (hashp->BSIZE << BYTE_SHIFT)); if (bit >= hashp->LAST_FREED) hashp->LAST_FREED = bit - 1; /* Calculate the split number for this page */ for (i = 0; (i < splitnum) && (bit > hashp->SPARES[i]); i++); offset = (i ? bit - hashp->SPARES[i - 1] : bit); if (offset >= SPLITMASK) { (void)!write(STDERR_FILENO, OVMSG, sizeof(OVMSG) - 1); errno = EFBIG; return (0); /* Out of overflow pages */ } addr = OADDR_OF(i, offset); #ifdef DEBUG2 (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n", addr, tmp1, tmp2); #endif /* ifdef DEBUG2 */ /* Allocate and return the overflow page */ return (addr); } /* * Mark this overflow page as free. */ void __free_ovflpage(HTAB *hashp, BUFHEAD *obufp) { u_int16_t addr; u_int32_t *freep; int bit_address, free_page, free_bit; u_int16_t ndx; addr = obufp->addr; #ifdef DEBUG1 (void)fprintf(stderr, "Freeing %d\n", addr); #endif /* ifdef DEBUG1 */ ndx = (((u_int16_t)addr) >> SPLITSHIFT); bit_address = (ndx ? hashp->SPARES[ndx - 1] : 0) + (addr & SPLITMASK) - 1; if (bit_address < hashp->LAST_FREED) hashp->LAST_FREED = bit_address; free_page = (bit_address >> (hashp->BSHIFT + BYTE_SHIFT)); free_bit = bit_address & ((hashp->BSIZE << BYTE_SHIFT) - 1); if (!(freep = hashp->mapp[free_page])) freep = fetch_bitmap(hashp, free_page); #ifdef DEBUG /* * This had better never happen. It means we tried to read a bitmap * that has already had overflow pages allocated off it, and we * failed to read it from the file. */ if (!freep) assert(0); #endif /* ifdef DEBUG */ CLRBIT(freep, free_bit); #ifdef DEBUG2 (void)fprintf(stderr, "FREE_OVFLPAGE: ADDR: %d BIT: %d PAGE %d\n", obufp->addr, free_bit, free_page); #endif /* ifdef DEBUG2 */ __reclaim_buf(hashp, obufp); } /* * Returns: * 0 success * -1 failure */ static int open_temp(HTAB *hashp) { sigset_t set, oset; int len; char *envtmp = NULL; char path[PATH_MAX]; if (issetugid() == 0) envtmp = getenv("TMPDIR"); len = snprintf(path, sizeof(path), "%s/_hash.XXXXXX", envtmp ? envtmp : "/tmp"); if (len < 0 || len >= sizeof(path)) { errno = ENAMETOOLONG; return (-1); } /* Block signals; make sure file goes away at process exit. */ (void)sigfillset(&set); (void)sigprocmask(SIG_BLOCK, &set, &oset); #if defined(_AIX) || defined(__solaris__) || defined(__managarm__) if ((hashp->fp = mkstemp(path)) != -1) { #else if ((hashp->fp = mkostemp(path, O_CLOEXEC)) != -1) { #endif /* if defined(_AIX) || defined(__solaris__) */ (void)unlink(path); } (void)sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL); return (hashp->fp != -1 ? 0 : -1); } /* * We have to know that the key will fit, but the last entry on the page is * an overflow pair, so we need to shift things. */ static void squeeze_key(u_int16_t *sp, const DBT *key, const DBT *val) { char *p; u_int16_t free_space, n, off, pageno; p = (char *)sp; n = sp[0]; free_space = FREESPACE(sp); off = OFFSET(sp); pageno = sp[n - 1]; off -= key->size; sp[n - 1] = off; memmove(p + off, key->data, key->size); off -= val->size; sp[n] = off; memmove(p + off, val->data, val->size); sp[0] = n + 2; sp[n + 1] = pageno; sp[n + 2] = OVFLPAGE; FREESPACE(sp) = free_space - PAIRSIZE(key, val); OFFSET(sp) = off; } static u_int32_t * fetch_bitmap(HTAB *hashp, int ndx) { if (ndx >= hashp->nmaps) return (NULL); if ((hashp->mapp[ndx] = (u_int32_t *)malloc(hashp->BSIZE)) == NULL) return (NULL); if (__get_page(hashp, (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) { free(hashp->mapp[ndx]); return (NULL); } return (hashp->mapp[ndx]); } #ifdef DEBUG4 int print_chain(int addr) { BUFHEAD *bufp; short *bp, oaddr; (void)fprintf(stderr, "%d ", addr); bufp = __get_buf(hashp, addr, NULL, 0); bp = (short *)bufp->page; while (bp[0] && ((bp[bp[0]] == OVFLPAGE) || ((bp[0] > 2) && bp[2] < REAL_KEY))) { oaddr = bp[bp[0] - 1]; (void)fprintf(stderr, "%d ", (int)oaddr); bufp = __get_buf(hashp, (int)oaddr, bufp, 0); bp = (short *)bufp->page; } (void)fprintf(stderr, "\n"); } #endif /* ifdef DEBUG4 */ ================================================ FILE: db/hash/ndbm.c ================================================ /* $OpenBSD: ndbm.c,v 1.28 2023/02/17 17:59:36 miod Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "bsd_ndbm.h" #include "hash.h" #undef open /* * * This package provides dbm and ndbm compatible interfaces to DB. * First are the DBM routines, which call the NDBM routines, and * the NDBM routines, which call the DB routines. */ static DBM *_dbm_open(const char *, const char *, int, mode_t); /* * Returns: * *DBM on success * NULL on failure */ static DBM * _dbm_open(const char *file, const char *suff, int flags, mode_t mode) { HASHINFO info; char path[PATH_MAX]; int len; len = snprintf(path, sizeof path, "%s%s", file, suff); if (len < 0 || len >= sizeof path) { errno = ENAMETOOLONG; return (NULL); } /* O_WRONLY not supported by db(3) but traditional ndbm allowed it. */ if ((flags & O_ACCMODE) == O_WRONLY) { flags &= ~O_WRONLY; flags |= O_RDWR; } info.bsize = 4096; info.ffactor = 40; info.nelem = 1; info.cachesize = 0; info.hash = NULL; info.lorder = 0; return ((DBM *)__hash_open(path, flags, mode, &info, 0)); } /* * Returns: * *DBM on success * NULL on failure */ DBM * dbm_open(const char *file, int flags, mode_t mode) { return(_dbm_open(file, DBM_SUFFIX, flags, mode)); } /* * Returns: * Nothing. */ void dbm_close(DBM *db) { (void)(db->close)(db); } DEF_WEAK(dbm_close); /* * Returns: * DATUM on success * NULL on failure */ datum dbm_fetch(DBM *db, datum key) { datum retdata; int status; DBT dbtkey, dbtretdata; dbtkey.data = key.dptr; dbtkey.size = key.dsize; status = (db->get)(db, &dbtkey, &dbtretdata, 0); if (status) { dbtretdata.data = NULL; dbtretdata.size = 0; } retdata.dptr = dbtretdata.data; retdata.dsize = dbtretdata.size; return (retdata); } DEF_WEAK(dbm_fetch); /* * Returns: * DATUM on success * NULL on failure */ datum dbm_firstkey(DBM *db) { int status; datum retkey; DBT dbtretkey, dbtretdata; status = (db->seq)(db, &dbtretkey, &dbtretdata, R_FIRST); if (status) dbtretkey.data = NULL; retkey.dptr = dbtretkey.data; retkey.dsize = dbtretkey.size; return (retkey); } DEF_WEAK(dbm_firstkey); /* * Returns: * DATUM on success * NULL on failure */ datum dbm_nextkey(DBM *db) { int status; datum retkey; DBT dbtretkey, dbtretdata; status = (db->seq)(db, &dbtretkey, &dbtretdata, R_NEXT); if (status) dbtretkey.data = NULL; retkey.dptr = dbtretkey.data; retkey.dsize = dbtretkey.size; return (retkey); } DEF_WEAK(dbm_nextkey); /* * Returns: * 0 on success * <0 on failure */ int dbm_delete(DBM *db, datum key) { int status; DBT dbtkey; dbtkey.data = key.dptr; dbtkey.size = key.dsize; status = (db->del)(db, &dbtkey, 0); if (status) return (-1); else return (0); } DEF_WEAK(dbm_delete); /* * Returns: * 0 on success * <0 on failure * 1 if DBM_INSERT and entry exists */ int dbm_store(DBM *db, datum key, datum data, int flags) { DBT dbtkey, dbtdata; dbtkey.data = key.dptr; dbtkey.size = key.dsize; dbtdata.data = data.dptr; dbtdata.size = data.dsize; return ((db->put)(db, &dbtkey, &dbtdata, (flags == DBM_INSERT) ? R_NOOVERWRITE : 0)); } DEF_WEAK(dbm_store); int dbm_error(DBM *db) { HTAB *hp; hp = (HTAB *)db->internal; return (hp->err); } int dbm_clearerr(DBM *db) { HTAB *hp; hp = (HTAB *)db->internal; hp->err = 0; return (0); } int dbm_dirfno(DBM *db) { return(((HTAB *)db->internal)->fp); } int dbm_rdonly(DBM *dbp) { HTAB *hashp = (HTAB *)dbp->internal; /* Could use DBM_RDONLY instead if we wanted... */ return ((hashp->flags & O_ACCMODE) == O_RDONLY); } DEF_WEAK(dbm_rdonly); ================================================ FILE: db/hash/page.h ================================================ /* $OpenBSD: page.h,v 1.6 2003/06/02 20:18:34 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Margo Seltzer. * * 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. * * @(#)page.h 8.2 (Berkeley) 5/31/94 */ /* * Definitions for hashing page file format. */ /* * routines dealing with a data page * * page format: * +------------------------------+ * p | n | keyoff | datoff | keyoff | * +------------+--------+--------+ * | datoff | free | ptr | --> | * +--------+---------------------+ * | F R E E A R E A | * +--------------+---------------+ * | <---- - - - | data | * +--------+-----+----+----------+ * | key | data | key | * +--------+----------+----------+ * * Pointer to the free space is always: p[p[0] + 2] * Amount of free space on the page is: p[p[0] + 1] */ /* * How many bytes required for this pair? * 2 shorts in the table at the top of the page + room for the * key and room for the data * * We prohibit entering a pair on a page unless there is also room to append * an overflow page. The reason for this it that you can get in a situation * where a single key/data pair fits on a page, but you can't append an * overflow page and later you'd have to split the key/data and handle like * a big pair. * You might as well do this up front. */ #define PAIRSIZE(K,D) (2*sizeof(u_int16_t) + (K)->size + (D)->size) #define BIGOVERHEAD (4*sizeof(u_int16_t)) #define KEYSIZE(K) (4*sizeof(u_int16_t) + (K)->size); #define OVFLSIZE (2*sizeof(u_int16_t)) #define FREESPACE(P) ((P)[(P)[0]+1]) #define OFFSET(P) ((P)[(P)[0]+2]) #define PAIRFITS(P,K,D) \ (((P)[2] >= REAL_KEY) && \ (PAIRSIZE((K),(D)) + OVFLSIZE) <= FREESPACE((P))) #define PAGE_META(N) (((N)+3) * sizeof(u_int16_t)) typedef struct { BUFHEAD *newp; BUFHEAD *oldp; BUFHEAD *nextp; u_int16_t next_addr; } SPLIT_RETURN; ================================================ FILE: db/mpool/mpool.c ================================================ /* $OpenBSD: mpool.c,v 1.21 2015/11/01 03:45:28 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #define __MPOOLINTERFACE_PRIVATE #include #undef open static BKT *mpool_bkt(MPOOL *); static BKT *mpool_look(MPOOL *, pgno_t); static int mpool_write(MPOOL *, BKT *); /* * mpool_open -- * Initialize a memory pool. */ MPOOL * mpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache) { struct stat sb; MPOOL *mp; int entry; /* * Get information about the file. * * XXX * We don't currently handle pipes, although we should. */ if (fstat(fd, &sb)) return (NULL); if (!S_ISREG(sb.st_mode)) { errno = ESPIPE; return (NULL); } /* Allocate and initialize the MPOOL cookie. */ if ((mp = (MPOOL *)calloc(1, sizeof(MPOOL))) == NULL) return (NULL); TAILQ_INIT(&mp->lqh); for (entry = 0; entry < HASHSIZE; ++entry) TAILQ_INIT(&mp->hqh[entry]); mp->maxcache = maxcache; mp->npages = sb.st_size / pagesize; mp->pagesize = pagesize; mp->fd = fd; return (mp); } /* * mpool_filter -- * Initialize input/output filters. */ void mpool_filter(MPOOL *mp, void (*pgin) (void *, pgno_t, void *), void (*pgout) (void *, pgno_t, void *), void *pgcookie) { mp->pgin = pgin; mp->pgout = pgout; mp->pgcookie = pgcookie; } /* * mpool_new -- * Get a new page of memory. */ void * mpool_new(MPOOL *mp, pgno_t *pgnoaddr, unsigned int flags) { struct _hqh *head; BKT *bp; if (mp->npages == MAX_PAGE_NUMBER) { (void)fprintf(stderr, "mpool_new: page allocation overflow.\n"); abort(); } #ifdef STATISTICS ++mp->pagenew; #endif /* ifdef STATISTICS */ /* * Get a BKT from the cache. Assign a new page number, attach * it to the head of the hash chain, the tail of the lru chain, * and return. */ if ((bp = mpool_bkt(mp)) == NULL) return (NULL); if (flags == MPOOL_PAGE_REQUEST) { mp->npages++; bp->pgno = *pgnoaddr; } else bp->pgno = *pgnoaddr = mp->npages++; bp->flags = MPOOL_PINNED | MPOOL_INUSE; head = &mp->hqh[HASHKEY(bp->pgno)]; TAILQ_INSERT_HEAD(head, bp, hq); TAILQ_INSERT_TAIL(&mp->lqh, bp, q); return (bp->page); } int mpool_delete(MPOOL *mp, void *page) { struct _hqh *head; BKT *bp; bp = (BKT *)((char *)page - sizeof(BKT)); #ifdef DEBUG if (!(bp->flags & MPOOL_PINNED)) { (void)fprintf(stderr, "mpool_delete: page %d not pinned\n", bp->pgno); abort(); } #endif /* ifdef DEBUG */ /* Remove from the hash and lru queues. */ head = &mp->hqh[HASHKEY(bp->pgno)]; TAILQ_REMOVE(head, bp, hq); TAILQ_REMOVE(&mp->lqh, bp, q); free(bp); mp->curcache--; return (RET_SUCCESS); } /* * mpool_get * Get a page. */ void * mpool_get(MPOOL *mp, pgno_t pgno, unsigned int flags) /* XXX not used? */ { struct _hqh *head; BKT *bp; off_t off; int nr; #ifdef STATISTICS ++mp->pageget; #endif /* ifdef STATISTICS */ /* Check for a page that is cached. */ if ((bp = mpool_look(mp, pgno)) != NULL) { #ifdef DEBUG if (!(flags & MPOOL_IGNOREPIN) && bp->flags & MPOOL_PINNED) { (void)fprintf(stderr, "mpool_get: page %d already pinned\n", bp->pgno); abort(); } #endif /* ifdef DEBUG */ /* * Move the page to the head of the hash chain and the tail * of the lru chain. */ head = &mp->hqh[HASHKEY(bp->pgno)]; TAILQ_REMOVE(head, bp, hq); TAILQ_INSERT_HEAD(head, bp, hq); TAILQ_REMOVE(&mp->lqh, bp, q); TAILQ_INSERT_TAIL(&mp->lqh, bp, q); /* Return a pinned page. */ bp->flags |= MPOOL_PINNED; return (bp->page); } /* Get a page from the cache. */ if ((bp = mpool_bkt(mp)) == NULL) return (NULL); /* Read in the contents. */ off = mp->pagesize * pgno; if ((nr = pread(mp->fd, bp->page, mp->pagesize, off)) != mp->pagesize) { switch (nr) { case -1: /* errno is set for us by pread(). */ free(bp); mp->curcache--; return (NULL); case 0: /* * A zero-length read means you need to create a * new page. */ memset(bp->page, 0, mp->pagesize); break; default: /* A partial read is definitely bad. */ free(bp); mp->curcache--; errno = EINVAL; return (NULL); } } #ifdef STATISTICS ++mp->pageread; #endif /* ifdef STATISTICS */ /* Set the page number, pin the page. */ bp->pgno = pgno; if (!(flags & MPOOL_IGNOREPIN)) bp->flags = MPOOL_PINNED; bp->flags |= MPOOL_INUSE; /* * Add the page to the head of the hash chain and the tail * of the lru chain. */ head = &mp->hqh[HASHKEY(bp->pgno)]; TAILQ_INSERT_HEAD(head, bp, hq); TAILQ_INSERT_TAIL(&mp->lqh, bp, q); /* Run through the user's filter. */ if (mp->pgin != NULL) (mp->pgin)(mp->pgcookie, bp->pgno, bp->page); return (bp->page); } /* * mpool_put * Return a page. */ int mpool_put(MPOOL *mp, void *page, unsigned int flags) { BKT *bp; #ifdef STATISTICS ++mp->pageput; #endif /* ifdef STATISTICS */ bp = (BKT *)((char *)page - sizeof(BKT)); #ifdef DEBUG if (!(bp->flags & MPOOL_PINNED)) { (void)fprintf(stderr, "mpool_put: page %d not pinned\n", bp->pgno); abort(); } #endif /* ifdef DEBUG */ bp->flags &= ~MPOOL_PINNED; if (flags & MPOOL_DIRTY) bp->flags |= flags & MPOOL_DIRTY; return (RET_SUCCESS); } /* * mpool_close * Close the buffer pool. */ int mpool_close(MPOOL *mp) { BKT *bp; /* Free up any space allocated to the lru pages. */ while ((bp = TAILQ_FIRST(&mp->lqh))) { TAILQ_REMOVE(&mp->lqh, bp, q); free(bp); } /* Free the MPOOL cookie. */ free(mp); return (RET_SUCCESS); } /* * mpool_sync * Sync the pool to disk. */ int mpool_sync(MPOOL *mp) { BKT *bp; /* Walk the lru chain, flushing any dirty pages to disk. */ TAILQ_FOREACH(bp, &mp->lqh, q) if (bp->flags & MPOOL_DIRTY && mpool_write(mp, bp) == RET_ERROR) return (RET_ERROR); /* Sync the file descriptor. */ return (fsync(mp->fd) ? RET_ERROR : RET_SUCCESS); } /* * mpool_bkt * Get a page from the cache (or create one). */ static BKT * mpool_bkt(MPOOL *mp) { struct _hqh *head; BKT *bp; /* If under the max cached, always create a new page. */ if (mp->curcache < mp->maxcache) goto new; /* * If the cache is max'd out, walk the lru list for a buffer we * can flush. If we find one, write it (if necessary) and take it * off any lists. If we don't find anything we grow the cache anyway. * The cache never shrinks. */ TAILQ_FOREACH(bp, &mp->lqh, q) if (!(bp->flags & MPOOL_PINNED)) { /* Flush if dirty. */ if (bp->flags & MPOOL_DIRTY && mpool_write(mp, bp) == RET_ERROR) return (NULL); #ifdef STATISTICS ++mp->pageflush; #endif /* ifdef STATISTICS */ /* Remove from the hash and lru queues. */ head = &mp->hqh[HASHKEY(bp->pgno)]; TAILQ_REMOVE(head, bp, hq); TAILQ_REMOVE(&mp->lqh, bp, q); #ifdef DEBUG { void *spage; spage = bp->page; memset(bp, 0xff, sizeof(BKT) + mp->pagesize); bp->page = spage; } #endif /* ifdef DEBUG */ bp->flags = 0; return (bp); } new: if ((bp = (BKT *)malloc(sizeof(BKT) + mp->pagesize)) == NULL) return (NULL); #ifdef STATISTICS ++mp->pagealloc; #endif /* ifdef STATISTICS */ memset(bp, 0xff, sizeof(BKT) + mp->pagesize); bp->page = (char *)bp + sizeof(BKT); bp->flags = 0; ++mp->curcache; return (bp); } /* * mpool_write * Write a page to disk. */ static int mpool_write(MPOOL *mp, BKT *bp) { off_t off; #ifdef STATISTICS ++mp->pagewrite; #endif /* ifdef STATISTICS */ /* Run through the user's filter. */ if (mp->pgout) (mp->pgout)(mp->pgcookie, bp->pgno, bp->page); off = mp->pagesize * bp->pgno; if (pwrite(mp->fd, bp->page, mp->pagesize, off) != mp->pagesize) return (RET_ERROR); /* * Re-run through the input filter since this page may soon be * accessed via the cache, and whatever the user's output filter * did may screw things up if we don't let the input filter * restore the in-core copy. */ if (mp->pgin) (mp->pgin)(mp->pgcookie, bp->pgno, bp->page); bp->flags &= ~MPOOL_DIRTY; return (RET_SUCCESS); } /* * mpool_look * Lookup a page in the cache. */ static BKT * mpool_look(MPOOL *mp, pgno_t pgno) { struct _hqh *head; BKT *bp; head = &mp->hqh[HASHKEY(pgno)]; TAILQ_FOREACH(bp, head, hq) if ((bp->pgno == pgno) && ((bp->flags & MPOOL_INUSE) == MPOOL_INUSE)) { #ifdef STATISTICS ++mp->cachehit; #endif /* ifdef STATISTICS */ return (bp); } #ifdef STATISTICS ++mp->cachemiss; #endif /* ifdef STATISTICS */ return (NULL); } #ifdef STATISTICS /* * mpool_stat * Print out cache statistics. */ void mpool_stat(MPOOL *mp) { BKT *bp; int cnt; char *sep; (void)fprintf(stderr, "%lu pages in the file\n", (unsigned long)mp->npages); (void)fprintf(stderr, "page size %lu, caching %lu pages of %lu page max cache\n", (unsigned long)mp->pagesize, (unsigned long)mp->curcache, (unsigned long)mp->maxcache); (void)fprintf(stderr, "%lu page puts, %lu page gets, %lu page new\n", mp->pageput, mp->pageget, mp->pagenew); (void)fprintf(stderr, "%lu page allocs, %lu page flushes\n", mp->pagealloc, mp->pageflush); if (mp->cachehit + mp->cachemiss) (void)fprintf(stderr, "%.0f%% cache hit rate (%lu hits, %lu misses)\n", ((double)mp->cachehit / (mp->cachehit + mp->cachemiss)) * 100, mp->cachehit, mp->cachemiss); (void)fprintf(stderr, "%lu page reads, %lu page writes\n", mp->pageread, mp->pagewrite); sep = ""; cnt = 0; TAILQ_FOREACH(bp, &mp->lqh, q) { (void)fprintf(stderr, "%s%d", sep, bp->pgno); if (bp->flags & MPOOL_DIRTY) (void)fprintf(stderr, "d"); if (bp->flags & MPOOL_PINNED) (void)fprintf(stderr, "P"); if (++cnt == 10) { sep = "\n"; cnt = 0; } else sep = ", "; } (void)fprintf(stderr, "\n"); } #endif /* ifdef STATISTICS */ ================================================ FILE: db/recno/extern.h ================================================ /* $OpenBSD: extern.h,v 1.7 2015/08/27 04:37:09 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)extern.h 8.3 (Berkeley) 6/4/94 */ #include "../btree/extern.h" __BEGIN_HIDDEN_DECLS int __rec_close(DB *); int __rec_delete(const DB *, const DBT *, unsigned int); int __rec_dleaf(BTREE *, PAGE *, u_int32_t); int __rec_fd(const DB *); int __rec_fmap(BTREE *, recno_t); int __rec_fout(BTREE *); int __rec_fpipe(BTREE *, recno_t); int __rec_get(const DB *, const DBT *, DBT *, unsigned int); int __rec_iput(BTREE *, recno_t, const DBT *, unsigned int); int __rec_put(const DB *dbp, DBT *, const DBT *, unsigned int); int __rec_ret(BTREE *, EPG *, recno_t, DBT *, DBT *); EPG *__rec_search(BTREE *, recno_t, enum SRCHOP); int __rec_seq(const DB *, DBT *, DBT *, unsigned int); int __rec_sync(const DB *, unsigned int); int __rec_vmap(BTREE *, recno_t); int __rec_vout(BTREE *); int __rec_vpipe(BTREE *, recno_t); __END_HIDDEN_DECLS ================================================ FILE: db/recno/rec_close.c ================================================ /* $OpenBSD: rec_close.c,v 1.13 2016/09/21 04:38:56 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #include "recno.h" /* * __REC_CLOSE -- Close a recno tree. * * Parameters: * dbp: pointer to access method * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_close(DB *dbp) { BTREE *t; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } if (__rec_sync(dbp, 0) == RET_ERROR) return (RET_ERROR); /* Committed to closing. */ status = RET_SUCCESS; if (!F_ISSET(t, R_INMEM)) { if (F_ISSET(t, R_CLOSEFP)) { if (fclose(t->bt_rfp)) status = RET_ERROR; } else { if (close(t->bt_rfd)) status = RET_ERROR; } } if (__bt_close(dbp) == RET_ERROR) status = RET_ERROR; return (status); } /* * __REC_SYNC -- sync the recno tree to disk. * * Parameters: * dbp: pointer to access method * * Returns: * RET_SUCCESS, RET_ERROR. */ int __rec_sync(const DB *dbp, unsigned int flags) { struct iovec iov[2]; BTREE *t; DBT data, key; off_t off; recno_t scursor, trec; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } if (flags == R_RECNOSYNC) return (__bt_sync(dbp, 0)); if (F_ISSET(t, R_RDONLY | R_INMEM) || !F_ISSET(t, R_MODIFIED)) return (RET_SUCCESS); /* Read any remaining records into the tree. */ if (!F_ISSET(t, R_EOF) && t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) return (RET_ERROR); /* Rewind the file descriptor. */ if (lseek(t->bt_rfd, 0, SEEK_SET) != 0) return (RET_ERROR); /* Save the cursor. */ scursor = t->bt_cursor.rcursor; key.size = sizeof(recno_t); key.data = &trec; if (F_ISSET(t, R_FIXLEN)) { /* * We assume that fixed length records are all fixed length. * Any that aren't are either EINVAL'd or corrected by the * record put code. */ status = (dbp->seq)(dbp, &key, &data, R_FIRST); while (status == RET_SUCCESS) { if (write(t->bt_rfd, data.data, data.size) != data.size) return (RET_ERROR); status = (dbp->seq)(dbp, &key, &data, R_NEXT); } } else { iov[1].iov_base = &t->bt_bval; iov[1].iov_len = 1; status = (dbp->seq)(dbp, &key, &data, R_FIRST); while (status == RET_SUCCESS) { iov[0].iov_base = data.data; iov[0].iov_len = data.size; if (writev(t->bt_rfd, iov, 2) != data.size + 1) return (RET_ERROR); status = (dbp->seq)(dbp, &key, &data, R_NEXT); } } /* Restore the cursor. */ t->bt_cursor.rcursor = scursor; if (status == RET_ERROR) return (RET_ERROR); if ((off = lseek(t->bt_rfd, 0, SEEK_CUR)) == -1) return (RET_ERROR); if (ftruncate(t->bt_rfd, off)) return (RET_ERROR); F_CLR(t, R_MODIFIED); return (RET_SUCCESS); } ================================================ FILE: db/recno/rec_delete.c ================================================ /* $OpenBSD: rec_delete.c,v 1.10 2005/08/05 13:03:00 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include "recno.h" static int rec_rdelete(BTREE *, recno_t); /* * __REC_DELETE -- Delete the item(s) referenced by a key. * * Parameters: * dbp: pointer to access method * key: key to delete * flags: R_CURSOR if deleting what the cursor references * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. */ int __rec_delete(const DB *dbp, const DBT *key, unsigned int flags) { BTREE *t; recno_t nrec; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } switch(flags) { case 0: if ((nrec = *(recno_t *)key->data) == 0) goto einval; if (nrec > t->bt_nrecs) return (RET_SPECIAL); --nrec; status = rec_rdelete(t, nrec); break; case R_CURSOR: if (!F_ISSET(&t->bt_cursor, CURS_INIT)) goto einval; if (t->bt_nrecs == 0) return (RET_SPECIAL); status = rec_rdelete(t, t->bt_cursor.rcursor - 1); if (status == RET_SUCCESS) --t->bt_cursor.rcursor; break; default: einval: errno = EINVAL; return (RET_ERROR); } if (status == RET_SUCCESS) F_SET(t, B_MODIFIED | R_MODIFIED); return (status); } /* * REC_RDELETE -- Delete the data matching the specified key. * * Parameters: * tree: tree * nrec: record to delete * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. */ static int rec_rdelete(BTREE *t, recno_t nrec) { EPG *e; PAGE *h; int status; /* Find the record; __rec_search pins the page. */ if ((e = __rec_search(t, nrec, SDELETE)) == NULL) return (RET_ERROR); /* Delete the record. */ h = e->page; status = __rec_dleaf(t, h, e->index); if (status != RET_SUCCESS) { mpool_put(t->bt_mp, h, 0); return (status); } mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } /* * __REC_DLEAF -- Delete a single record from a recno leaf page. * * Parameters: * t: tree * idx: index on current page to delete * * Returns: * RET_SUCCESS, RET_ERROR. */ int __rec_dleaf(BTREE *t, PAGE *h, u_int32_t idx) { RLEAF *rl; indx_t *ip, cnt, offset; u_int32_t nbytes; char *from; void *to; /* * Delete a record from a recno leaf page. Internal records are never * deleted from internal pages, regardless of the records that caused * them to be added being deleted. Pages made empty by deletion are * not reclaimed. They are, however, made available for reuse. * * Pack the remaining entries at the end of the page, shift the indices * down, overwriting the deleted record and its index. If the record * uses overflow pages, make them available for reuse. */ to = rl = GETRLEAF(h, idx); if (rl->flags & P_BIGDATA && __ovfl_delete(t, rl->bytes) == RET_ERROR) return (RET_ERROR); nbytes = NRLEAF(rl); /* * Compress the key/data pairs. Compress and adjust the [BR]LEAF * offsets. Reset the headers. */ from = (char *)h + h->upper; memmove(from + nbytes, from, (char *)to - from); h->upper += nbytes; offset = h->linp[idx]; for (cnt = &h->linp[idx] - (ip = &h->linp[0]); cnt--; ++ip) if (ip[0] < offset) ip[0] += nbytes; for (cnt = &h->linp[NEXTINDEX(h)] - ip; --cnt; ++ip) ip[0] = ip[1] < offset ? ip[1] + nbytes : ip[1]; h->lower -= sizeof(indx_t); --t->bt_nrecs; return (RET_SUCCESS); } ================================================ FILE: db/recno/rec_get.c ================================================ /* $OpenBSD: rec_get.c,v 1.11 2007/08/08 07:16:50 ray Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #include "recno.h" /* * __REC_GET -- Get a record from the btree. * * Parameters: * dbp: pointer to access method * key: key to find * data: data to return * flag: currently unused * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key not found. */ int __rec_get(const DB *dbp, const DBT *key, DBT *data, unsigned int flags) { BTREE *t; EPG *e; recno_t nrec; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* Get currently doesn't take any flags, and keys of 0 are illegal. */ if (flags || (nrec = *(recno_t *)key->data) == 0) { errno = EINVAL; return (RET_ERROR); } /* * If we haven't seen this record yet, try to find it in the * original file. */ if (nrec > t->bt_nrecs) { if (F_ISSET(t, R_EOF | R_INMEM)) return (RET_SPECIAL); if ((status = t->bt_irec(t, nrec)) != RET_SUCCESS) return (status); } --nrec; if ((e = __rec_search(t, nrec, SEARCH)) == NULL) return (RET_ERROR); status = __rec_ret(t, e, 0, NULL, data); if (F_ISSET(t, B_DB_LOCK)) mpool_put(t->bt_mp, e->page, 0); else t->bt_pinned = e->page; return (status); } /* * __REC_FPIPE -- Get fixed length records from a pipe. * * Parameters: * t: tree * cnt: records to read * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_fpipe(BTREE *t, recno_t top) { DBT data; recno_t nrec; size_t len; int ch; unsigned char *p; void *tp; if (t->bt_rdata.size < t->bt_reclen) { tp = realloc(t->bt_rdata.data, t->bt_reclen); if (tp == NULL) return (RET_ERROR); t->bt_rdata.data = tp; t->bt_rdata.size = t->bt_reclen; } data.data = t->bt_rdata.data; data.size = t->bt_reclen; for (nrec = t->bt_nrecs; nrec < top;) { len = t->bt_reclen; for (p = t->bt_rdata.data;; *p++ = ch) if ((ch = getc(t->bt_rfp)) == EOF || !--len) { if (ch != EOF) *p = ch; if (len != 0) memset(p, t->bt_bval, len); if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) return (RET_ERROR); ++nrec; break; } if (ch == EOF) break; } if (nrec < top) { F_SET(t, R_EOF); return (RET_SPECIAL); } return (RET_SUCCESS); } /* * __REC_VPIPE -- Get variable length records from a pipe. * * Parameters: * t: tree * cnt: records to read * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_vpipe(BTREE *t, recno_t top) { DBT data; recno_t nrec; size_t len; size_t sz; int bval, ch; unsigned char *p; void *tp; bval = t->bt_bval; for (nrec = t->bt_nrecs; nrec < top; ++nrec) { for (p = t->bt_rdata.data, sz = t->bt_rdata.size;; *p++ = ch, --sz) { if ((ch = getc(t->bt_rfp)) == EOF || ch == bval) { data.data = t->bt_rdata.data; data.size = p - (unsigned char *)t->bt_rdata.data; if (ch == EOF && data.size == 0) break; if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) return (RET_ERROR); break; } if (sz == 0) { len = p - (unsigned char *)t->bt_rdata.data; t->bt_rdata.size += (sz = 256); tp = realloc(t->bt_rdata.data, t->bt_rdata.size); if (tp == NULL) return (RET_ERROR); t->bt_rdata.data = tp; p = (unsigned char *)t->bt_rdata.data + len; } } if (ch == EOF) break; } if (nrec < top) { F_SET(t, R_EOF); return (RET_SPECIAL); } return (RET_SUCCESS); } /* * __REC_FMAP -- Get fixed length records from a file. * * Parameters: * t: tree * cnt: records to read * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_fmap(BTREE *t, recno_t top) { DBT data; recno_t nrec; unsigned char *sp, *ep, *p; size_t len; void *tp; if (t->bt_rdata.size < t->bt_reclen) { tp = realloc(t->bt_rdata.data, t->bt_reclen); if (tp == NULL) return (RET_ERROR); t->bt_rdata.data = tp; t->bt_rdata.size = t->bt_reclen; } data.data = t->bt_rdata.data; data.size = t->bt_reclen; sp = (unsigned char *)t->bt_cmap; ep = (unsigned char *)t->bt_emap; for (nrec = t->bt_nrecs; nrec < top; ++nrec) { if (sp >= ep) { F_SET(t, R_EOF); return (RET_SPECIAL); } len = t->bt_reclen; for (p = t->bt_rdata.data; sp < ep && len > 0; *p++ = *sp++, --len); if (len != 0) memset(p, t->bt_bval, len); if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) return (RET_ERROR); } t->bt_cmap = (caddr_t)sp; return (RET_SUCCESS); } /* * __REC_VMAP -- Get variable length records from a file. * * Parameters: * t: tree * cnt: records to read * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_vmap(BTREE *t, recno_t top) { DBT data; unsigned char *sp, *ep; recno_t nrec; int bval; sp = (unsigned char *)t->bt_cmap; ep = (unsigned char *)t->bt_emap; bval = t->bt_bval; for (nrec = t->bt_nrecs; nrec < top; ++nrec) { if (sp >= ep) { F_SET(t, R_EOF); return (RET_SPECIAL); } for (data.data = sp; sp < ep && *sp != bval; ++sp); data.size = sp - (unsigned char *)data.data; if (__rec_iput(t, nrec, &data, 0) != RET_SUCCESS) return (RET_ERROR); ++sp; } t->bt_cmap = (caddr_t)sp; return (RET_SUCCESS); } ================================================ FILE: db/recno/rec_open.c ================================================ /* $OpenBSD: rec_open.c,v 1.14 2020/12/01 16:19:38 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Mike Olson. * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "recno.h" #undef open DB * __rec_open(const char *fname, int flags, int mode, const RECNOINFO *openinfo, int dflags) { BTREE *t; BTREEINFO btopeninfo; DB *dbp; PAGE *h; struct stat sb; int rfd, sverrno; /* Open the user's file -- if this fails, we're done. */ if (fname != NULL && (rfd = open(fname, flags, mode)) < 0) return (NULL); /* Create a btree in memory (backed by disk). */ dbp = NULL; if (openinfo) { if (openinfo->flags & ~(R_FIXEDLEN | R_NOKEY | R_SNAPSHOT)) goto einval; btopeninfo.flags = 0; btopeninfo.cachesize = openinfo->cachesize; btopeninfo.maxkeypage = 0; btopeninfo.minkeypage = 0; btopeninfo.psize = openinfo->psize; btopeninfo.compare = NULL; btopeninfo.prefix = NULL; btopeninfo.lorder = openinfo->lorder; dbp = __bt_open(openinfo->bfname, O_RDWR, S_IRUSR | S_IWUSR, &btopeninfo, dflags); } else dbp = __bt_open(NULL, O_RDWR, S_IRUSR | S_IWUSR, NULL, dflags); if (dbp == NULL) goto err; /* * Some fields in the tree structure are recno specific. Fill them * in and make the btree structure look like a recno structure. We * don't change the bt_ovflsize value, it's close enough and slightly * bigger. */ t = dbp->internal; if (openinfo) { if (openinfo->flags & R_FIXEDLEN) { F_SET(t, R_FIXLEN); t->bt_reclen = openinfo->reclen; if (t->bt_reclen == 0) goto einval; } t->bt_bval = openinfo->bval; } else t->bt_bval = '\n'; F_SET(t, R_RECNO); if (fname == NULL) F_SET(t, R_EOF | R_INMEM); else t->bt_rfd = rfd; if (fname != NULL) { /* * In 4.4BSD, stat(2) returns true for ISSOCK on pipes. * Unfortunately, that's not portable, so we use lseek * and check the errno values. */ errno = 0; if (lseek(rfd, 0, SEEK_CUR) == -1 && errno == ESPIPE) { switch (flags & O_ACCMODE) { case O_RDONLY: F_SET(t, R_RDONLY); break; default: goto einval; } slow: if ((t->bt_rfp = fdopen(rfd, "r")) == NULL) goto err; F_SET(t, R_CLOSEFP); t->bt_irec = F_ISSET(t, R_FIXLEN) ? __rec_fpipe : __rec_vpipe; } else { switch (flags & O_ACCMODE) { case O_RDONLY: F_SET(t, R_RDONLY); break; case O_RDWR: break; default: goto einval; } if (fstat(rfd, &sb)) goto err; if (sb.st_size == 0) F_SET(t, R_EOF); else { goto slow; } } } /* Use the recno routines. */ dbp->close = __rec_close; dbp->del = __rec_delete; dbp->fd = __rec_fd; dbp->get = __rec_get; dbp->put = __rec_put; dbp->seq = __rec_seq; dbp->sync = __rec_sync; dbp->type = DB_RECNO; /* If the root page was created, reset the flags. */ if ((h = mpool_get(t->bt_mp, P_ROOT, 0)) == NULL) goto err; if ((h->flags & P_TYPE) == P_BLEAF) { F_CLR(h, P_TYPE); F_SET(h, P_RLEAF); mpool_put(t->bt_mp, h, MPOOL_DIRTY); } else mpool_put(t->bt_mp, h, 0); if (openinfo && openinfo->flags & R_SNAPSHOT && !F_ISSET(t, R_EOF | R_INMEM) && t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) goto err; return (dbp); einval: errno = EINVAL; err: sverrno = errno; if (dbp != NULL) (void)__bt_close(dbp); if (fname != NULL) (void)close(rfd); errno = sverrno; return (NULL); } int __rec_fd(const DB *dbp) { BTREE *t; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* In-memory database can't have a file descriptor. */ if (F_ISSET(t, R_INMEM)) { errno = ENOENT; return (-1); } return (t->bt_rfd); } ================================================ FILE: db/recno/rec_put.c ================================================ /* $OpenBSD: rec_put.c,v 1.11 2007/08/08 07:16:50 ray Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "recno.h" /* * __REC_PUT -- Add a recno item to the tree. * * Parameters: * dbp: pointer to access method * key: key * data: data * flag: R_CURSOR, R_IAFTER, R_IBEFORE, R_NOOVERWRITE * * Returns: * RET_ERROR, RET_SUCCESS and RET_SPECIAL if the key is * already in the tree and R_NOOVERWRITE specified. */ int __rec_put(const DB *dbp, DBT *key, const DBT *data, unsigned int flags) { BTREE *t; DBT fdata, tdata; recno_t nrec; int status; void *tp; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } /* * If using fixed-length records, and the record is long, return * EINVAL. If it's short, pad it out. Use the record data return * memory, it's only short-term. */ if (F_ISSET(t, R_FIXLEN) && data->size != t->bt_reclen) { if (data->size > t->bt_reclen) goto einval; if (t->bt_rdata.size < t->bt_reclen) { tp = realloc(t->bt_rdata.data, t->bt_reclen); if (tp == NULL) return (RET_ERROR); t->bt_rdata.data = tp; t->bt_rdata.size = t->bt_reclen; } memmove(t->bt_rdata.data, data->data, data->size); memset((char *)t->bt_rdata.data + data->size, t->bt_bval, t->bt_reclen - data->size); fdata.data = t->bt_rdata.data; fdata.size = t->bt_reclen; } else { fdata.data = data->data; fdata.size = data->size; } switch (flags) { case R_CURSOR: if (!F_ISSET(&t->bt_cursor, CURS_INIT)) goto einval; nrec = t->bt_cursor.rcursor; break; case R_SETCURSOR: if ((nrec = *(recno_t *)key->data) == 0) goto einval; break; case R_IAFTER: if ((nrec = *(recno_t *)key->data) == 0) { nrec = 1; flags = R_IBEFORE; } break; case 0: case R_IBEFORE: if ((nrec = *(recno_t *)key->data) == 0) goto einval; break; case R_NOOVERWRITE: if ((nrec = *(recno_t *)key->data) == 0) goto einval; if (nrec <= t->bt_nrecs) return (RET_SPECIAL); break; default: einval: errno = EINVAL; return (RET_ERROR); } /* * Make sure that records up to and including the put record are * already in the database. If skipping records, create empty ones. */ if (nrec > t->bt_nrecs) { if (!F_ISSET(t, R_EOF | R_INMEM) && t->bt_irec(t, nrec) == RET_ERROR) return (RET_ERROR); if (nrec > t->bt_nrecs + 1) { if (F_ISSET(t, R_FIXLEN)) { if ((tdata.data = (void *)malloc(t->bt_reclen)) == NULL) return (RET_ERROR); tdata.size = t->bt_reclen; memset(tdata.data, t->bt_bval, tdata.size); } else { tdata.data = NULL; tdata.size = 0; } while (nrec > t->bt_nrecs + 1) if (__rec_iput(t, t->bt_nrecs, &tdata, 0) != RET_SUCCESS) return (RET_ERROR); if (F_ISSET(t, R_FIXLEN)) free(tdata.data); } } if ((status = __rec_iput(t, nrec - 1, &fdata, flags)) != RET_SUCCESS) return (status); if (flags == R_SETCURSOR) t->bt_cursor.rcursor = nrec; F_SET(t, R_MODIFIED); return (__rec_ret(t, NULL, nrec, key, NULL)); } /* * __REC_IPUT -- Add a recno item to the tree. * * Parameters: * t: tree * nrec: record number * data: data * * Returns: * RET_ERROR, RET_SUCCESS */ int __rec_iput(BTREE *t, recno_t nrec, const DBT *data, unsigned int flags) { DBT tdata; EPG *e; PAGE *h; indx_t idx, nxtindex; pgno_t pg; u_int32_t nbytes; int dflags, status; char *dest, db[NOVFLSIZE]; /* * If the data won't fit on a page, store it on indirect pages. * * XXX * If the insert fails later on, these pages aren't recovered. */ if (data->size > t->bt_ovflsize) { if (__ovfl_put(t, data, &pg) == RET_ERROR) return (RET_ERROR); tdata.data = db; tdata.size = NOVFLSIZE; *(pgno_t *)db = pg; *(u_int32_t *)(db + sizeof(pgno_t)) = data->size; dflags = P_BIGDATA; data = &tdata; } else dflags = 0; /* __rec_search pins the returned page. */ if ((e = __rec_search(t, nrec, nrec > t->bt_nrecs || flags == R_IAFTER || flags == R_IBEFORE ? SINSERT : SEARCH)) == NULL) return (RET_ERROR); h = e->page; idx = e->index; /* * Add the specified key/data pair to the tree. The R_IAFTER and * R_IBEFORE flags insert the key after/before the specified key. * * Pages are split as required. */ switch (flags) { case R_IAFTER: ++idx; break; case R_IBEFORE: break; default: if (nrec < t->bt_nrecs && __rec_dleaf(t, h, idx) == RET_ERROR) { mpool_put(t->bt_mp, h, 0); return (RET_ERROR); } break; } /* * If not enough room, split the page. The split code will insert * the key and data and unpin the current page. If inserting into * the offset array, shift the pointers up. */ nbytes = NRLEAFDBT(data->size); if (h->upper - h->lower < nbytes + sizeof(indx_t)) { status = __bt_split(t, h, NULL, data, dflags, nbytes, idx); if (status == RET_SUCCESS) ++t->bt_nrecs; return (status); } if (idx < (nxtindex = NEXTINDEX(h))) memmove(h->linp + idx + 1, h->linp + idx, (nxtindex - idx) * sizeof(indx_t)); h->lower += sizeof(indx_t); h->linp[idx] = h->upper -= nbytes; dest = (char *)h + h->upper; if (data == NULL) return (RET_ERROR); WR_RLEAF(dest, data, dflags); ++t->bt_nrecs; F_SET(t, B_MODIFIED); mpool_put(t->bt_mp, h, MPOOL_DIRTY); return (RET_SUCCESS); } ================================================ FILE: db/recno/rec_search.c ================================================ /* $OpenBSD: rec_search.c,v 1.11 2005/08/05 13:03:00 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * w * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "recno.h" /* * __REC_SEARCH -- Search a btree for a key. * * Parameters: * t: tree to search * recno: key to find * op: search operation * * Returns: * EPG for matching record, if any, or the EPG for the location of the * key, if it were inserted into the tree. * * Returns: * The EPG for matching record, if any, or the EPG for the location * of the key, if it were inserted into the tree, is entered into * the bt_cur field of the tree. A pointer to the field is returned. */ EPG * __rec_search(BTREE *t, recno_t recno, enum SRCHOP op) { indx_t idx; PAGE *h; EPGNO *parent; RINTERNAL *r; pgno_t pg; indx_t top; recno_t total; int sverrno; BT_CLR(t); for (pg = P_ROOT, total = 0;;) { if ((h = mpool_get(t->bt_mp, pg, 0)) == NULL) goto err; if (h->flags & P_RLEAF) { t->bt_cur.page = h; t->bt_cur.index = recno - total; return (&t->bt_cur); } for (idx = 0, top = NEXTINDEX(h);;) { r = GETRINTERNAL(h, idx); if (++idx == top || total + r->nrecs > recno) break; total += r->nrecs; } BT_PUSH(t, pg, idx - 1); pg = r->pgno; switch (op) { case SDELETE: --GETRINTERNAL(h, (idx - 1))->nrecs; mpool_put(t->bt_mp, h, MPOOL_DIRTY); break; case SINSERT: ++GETRINTERNAL(h, (idx - 1))->nrecs; mpool_put(t->bt_mp, h, MPOOL_DIRTY); break; case SEARCH: mpool_put(t->bt_mp, h, 0); break; } } /* Try and recover the tree. */ err: sverrno = errno; if (op != SEARCH) while ((parent = BT_POP(t)) != NULL) { if ((h = mpool_get(t->bt_mp, parent->pgno, 0)) == NULL) break; if (op == SINSERT) --GETRINTERNAL(h, parent->index)->nrecs; else ++GETRINTERNAL(h, parent->index)->nrecs; mpool_put(t->bt_mp, h, MPOOL_DIRTY); } errno = sverrno; return (NULL); } ================================================ FILE: db/recno/rec_seq.c ================================================ /* $OpenBSD: rec_seq.c,v 1.8 2005/08/05 13:03:00 espie Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include #include #include "recno.h" /* * __REC_SEQ -- Recno sequential scan interface. * * Parameters: * dbp: pointer to access method * key: key for positioning and return value * data: data return value * flags: R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV. * * Returns: * RET_ERROR, RET_SUCCESS or RET_SPECIAL if there's no next key. */ int __rec_seq(const DB *dbp, DBT *key, DBT *data, unsigned int flags) { BTREE *t; EPG *e; recno_t nrec; int status; t = dbp->internal; /* Toss any page pinned across calls. */ if (t->bt_pinned != NULL) { mpool_put(t->bt_mp, t->bt_pinned, 0); t->bt_pinned = NULL; } switch(flags) { case R_CURSOR: if ((nrec = *(recno_t *)key->data) == 0) goto einval; break; case R_NEXT: if (F_ISSET(&t->bt_cursor, CURS_INIT)) { nrec = t->bt_cursor.rcursor + 1; break; } /* FALLTHROUGH */ case R_FIRST: nrec = 1; break; case R_PREV: if (F_ISSET(&t->bt_cursor, CURS_INIT)) { if ((nrec = t->bt_cursor.rcursor - 1) == 0) return (RET_SPECIAL); break; } /* FALLTHROUGH */ case R_LAST: if (!F_ISSET(t, R_EOF | R_INMEM) && t->bt_irec(t, MAX_REC_NUMBER) == RET_ERROR) return (RET_ERROR); nrec = t->bt_nrecs; break; default: einval: errno = EINVAL; return (RET_ERROR); } if (t->bt_nrecs == 0 || nrec > t->bt_nrecs) { if (!F_ISSET(t, R_EOF | R_INMEM) && (status = t->bt_irec(t, nrec)) != RET_SUCCESS) return (status); if (t->bt_nrecs == 0 || nrec > t->bt_nrecs) return (RET_SPECIAL); } if ((e = __rec_search(t, nrec - 1, SEARCH)) == NULL) return (RET_ERROR); F_SET(&t->bt_cursor, CURS_INIT); t->bt_cursor.rcursor = nrec; status = __rec_ret(t, e, nrec, key, data); if (F_ISSET(t, B_DB_LOCK)) mpool_put(t->bt_mp, e->page, 0); else t->bt_pinned = e->page; return (status); } ================================================ FILE: db/recno/rec_utils.c ================================================ /* $OpenBSD: rec_utils.c,v 1.10 2022/12/17 17:10:06 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../../include/compat.h" #include #include #include #include #include #include "recno.h" /* * __rec_ret -- * Build return data. * * Parameters: * t: tree * e: key/data pair to be returned * nrec: record number * key: user's key structure * data: user's data structure * * Returns: * RET_SUCCESS, RET_ERROR. */ int __rec_ret(BTREE *t, EPG *e, recno_t nrec, DBT *key, DBT *data) { RLEAF *rl; void *p; if (key == NULL) goto dataonly; /* We have to copy the key, it's not on the page. */ if (sizeof(recno_t) > t->bt_rkey.size) { p = realloc(t->bt_rkey.data, sizeof(recno_t)); if (p == NULL) return (RET_ERROR); t->bt_rkey.data = p; t->bt_rkey.size = sizeof(recno_t); } memmove(t->bt_rkey.data, &nrec, sizeof(recno_t)); key->size = sizeof(recno_t); key->data = t->bt_rkey.data; dataonly: if (data == NULL) return (RET_SUCCESS); /* * We must copy big keys/data to make them contiguous. Otherwise, * leave the page pinned and don't copy unless the user specified * concurrent access. */ rl = GETRLEAF(e->page, e->index); if (rl->flags & P_BIGDATA) { if (__ovfl_get(t, rl->bytes, &data->size, &t->bt_rdata.data, &t->bt_rdata.size)) return (RET_ERROR); data->data = t->bt_rdata.data; } else if (F_ISSET(t, B_DB_LOCK)) { /* Use +1 in case the first record retrieved is 0 length. */ if (rl->dsize + 1 > t->bt_rdata.size) { p = realloc(t->bt_rdata.data, rl->dsize + 1); if (p == NULL) return (RET_ERROR); t->bt_rdata.data = p; t->bt_rdata.size = rl->dsize + 1; } memmove(t->bt_rdata.data, rl->bytes, rl->dsize); data->size = rl->dsize; data->data = t->bt_rdata.data; } else { data->size = rl->dsize; data->data = rl->bytes; } return (RET_SUCCESS); } ================================================ FILE: db/recno/recno.h ================================================ /* $OpenBSD: recno.h,v 1.5 2003/06/02 20:18:34 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)recno.h 8.1 (Berkeley) 6/4/93 */ enum SRCHOP { SDELETE, SINSERT, SEARCH}; /* Rec_search operation. */ #include "../btree/btree.h" #include "extern.h" ================================================ FILE: docs/USD.doc/edit/edit.vindex ================================================ .\" $OpenBSD: edit.vindex,v 1.3 2003/06/03 02:56:21 millert Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)edit.vindex 8.1 (Berkeley) 6/8/93 .\" .bd I .ND .TL Index .sp 3 .2C .nf addressing, \fIsee\fR line numbers append mode, 4 backslash (\\), 18 buffer, 2 command mode, 4 context search, 8, 10, 13, 18 control characters (``^'' notation), 8 control-d, 6 current filename, 19, 20 current line (.), 9, 15 diagnostic messages, 4 disk, 2 documentation, 21 edit (to begin editing session), 3, 7 editing commands: .in +2 append (a), 4, 7 change (c), 16 copy (co), 13 delete (d), 13-14 edit (e), 12 file (f), 19 global (g), 18-19 move (m), 12-13 number (nu), 9 preserve (pre), 20-21 print (p), 8 quit (q), 5, 11 quit! (q!), 11 read (r), 20 recover (rec), 20 substitute (s), 9-10, 17, 18 undo (u), 14, 17 write (w), 5-6, 11, 19-20 z, 11 .sp 10i ! (shell escape), 19 $= , 15 +, 15 \-, 15 //, 8, 18 ??, 18 \&\fB.\fR, 9, 15 \&\fB.\fR=, 9, 15 .in -2 erasing .ti +2 characters (#), 8 .ti +2 lines (@), 8 ex (text editor), 21 \fIEx Reference Manual\fR, 21 file, 1 file recovery, 20 filename, 2 Interrupt (message), 7 line numbers, \fIsee also\fR current line .ti +2 dollar sign ($), 8, 12-13, 15 .ti +2 dot (.), 9, 15 .ti +2 relative (+ and \-), 15, 16 logging out, 6 login procedure, 2 ``magic'' characters, 21 non-printing characters, 8 ``not found'' (message), 3 program, 1 recovery \fIsee\fR file recovery shell, 18 shell escape (!), 19 special characters (^, $, \e), 18 text input mode, 4 UNIX, 1 ================================================ FILE: docs/USD.doc/edit/edittut.ms ================================================ .\" $OpenBSD: edittut.ms,v 1.8 2022/12/26 19:16:03 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)edittut.ms 8.3 (Berkeley) 8/18/96 .\" .ie n \{\ .po 5n .ll 70n .\} .el \{\ .ll 6.5i .nr LL 6.5i .\} .EH 'USD:11-%''Ex: A Tutorial' .OH 'Ex: A Tutorial''USD:11-%' .LP .ds u \s-2UNIX\s0 .ND .sp 4 .ce \f3\s+2Ex: A Tutorial\s0\f1 .sp .ce 3 .I Ricki Blau .sp James Joyce .R .sp .ce 3 Computing Services University of California Berkeley, California 94720 .sp 3 .ce .I ABSTRACT .R .sp .LP This narrative introduction to the use of the text editor .I ex assumes no prior familiarity with computers or with text editing. Its aim is to lead the beginning \s-2UNIX*\s+2 user through the .FS *UNIX is a trademark of The Open Group. .FE fundamental steps of writing and revising a file of text. .\" Edit, .\" a version of the text editor .\" .I ex, .\" was designed to provide an informative environment .\" for new and casual users. .PP We welcome comments and suggestions about this tutorial and the \s-2UNIX\s+2 documentation in general. .sp 1v September 1981 .bp .ll 6.5i .nr LL 6.5i .nr LT 6.5i .ds u \s-2UNIX\s0 .ce \s+2\f3Contents\f1\s0 .LP .nf Introduction\ \ \ 3 .sp Session 1\ \ 4 .in +.5i Making contact with \s-2UNIX\s+2\ \ \ 4 Logging in\ \ 4 Asking for \fIex\fR\ \ \ 4 The ``Command not found'' message\ \ \ 5 A summary\ \ 5 Entering text\ \ \ 5 Messages from \fIex\fR\ \ \ 5 Text input mode\ \ \ 6 Making corrections\ \ \ 6 Writing text to disk\ \ \ 7 Signing off\ \ 7 .in -.5i .sp Session 2\ \ \ 8 .in +.5i Adding more text to the file\ \ \ 8 Interrupt\ \ \ 8 Making corrections\ \ \ 8 Listing what's in the buffer (p)\ \ \ 9 Finding things in the buffer\ \ \ 9 The current line\ \ \ 10 Numbering lines (nu)\ \ \ 10 Substitute command (s)\ \ \ 10 Another way to list what's in the buffer (z)\ \ \ 11 Saving the modified text\ \ \ 12 .in -.5i .sp Session 3\ \ \ 13 .in +.5i Bringing text into the buffer (e)\ \ \ 13 Moving text in the buffer (m)\ \ \ 13 Copying lines (copy)\ \ \ 14 Deleting lines (d)\ \ \ 14 A word or two of caution\ \ \ 15 Undo (u) to the rescue\ \ \ 15 More about the dot (.) and buffer end ($)\ \ \ 16 Moving around in the buffer (+ and \-)\ \ \ 16 Changing lines (c)\ \ \ 17 .in -.5i .sp Session 4\ \ \ 18 .in +.5i Making commands global (g)\ \ \ 18 More about searching and substituting\ \ \ 19 Special characters\ \ \ 19 Issuing \s-2UNIX\s+2 commands from the editor\ \ \ 20 Filenames and file manipulation\ \ \ 20 The file (f) command\ \ \ 20 Reading additional files (r)\ \ \ 21 Writing parts of the buffer\ \ \ 21 Recovering files\ \ \ 21 Other recovery techniques\ \ \ 21 Options, set, and editor startup files\ \ \ 22 Further reading and other information\ \ \ 22 .in -.5i .sp Index\ \ \ 23 .bp .SH .ce \s+2Introduction\s0 .PP Text editing using a terminal connected to a computer allows you to create, modify, and print text easily. A .I text editor .R is a program that assists you as you create and modify text. The text editor you will learn here is named .I ex . Creating text using .I ex is as easy as typing it on an electric typewriter. Modifying text involves telling the text editor what you want to add, change, or delete. You can review your text by typing a command to print the file contents as they are currently. Another program (which we do not discuss in this document), a text formatter, rearranges your text for you into ``finished form.'' .PP These lessons assume no prior familiarity with computers or with text editing. They consist of a series of text editing sessions which lead you through the fundamental steps of creating and revising text. After scanning each lesson and before beginning the next, you should try the examples at a terminal to get a feeling for the actual process of text editing. If you set aside some time for experimentation, you will soon become familiar with using the computer to write and modify text. In addition to the actual use of the text editor, other features of \s-2UNIX\s0 will be very important to your work. You can begin to learn about these other features by reading one of the other tutorials that provide a general introduction to the system. You will be ready to proceed with this lesson as soon as you are familiar with (1) your terminal and its special keys, (2) how to log in, (3) and the ways of correcting typing errors. Let's first define some terms: .sp .5 .IP program 12 A set of instructions, given to the computer, describing the sequence of steps the computer performs in order to accomplish a specific task. The task must be specific, such as balancing your checkbook or editing your text. A general task, such as working for world peace, is something we can all do, but not something we can currently write programs to do. .IP UNIX \s-2UNIX\s0 is a special type of program, called an operating system, that supervises the machinery and all other programs comprising the total computer system. .IP ex .I ex is the name of the \s-2UNIX\s0 text editor you will be learning to use, and is a program that aids you in writing or revising text. .\" Edit was designed for beginning users, .\" and is a simplified version of an editor named .\" .I ex. .IP file Each \s-2UNIX\s0 account is allotted space for the permanent storage of information, such as programs, data or text. A file is a logical unit of data, for example, an essay, a program, or a chapter from a book, which is stored on a computer system. Once you create a file, it is kept until you instruct the system to remove it. You may create a file during one \s-2UNIX\s0 session, end the session, and return to use it at a later time. Files contain anything you choose to write and store in them. The sizes of files vary to suit your needs; one file might hold only a single number, yet another might contain a very long document or program. The only way to save information from one session to the next is to store it in a file, which you will learn in Session 1. .IP filename Filenames are used to distinguish one file from another, serving the same purpose as the labels of manila folders in a file cabinet. In order to write or access information in a file, you use the name of that file in a \s-2UNIX\s0 command, and the system will automatically locate the file. .IP disk Files are stored on an input/output device called a disk, which looks something like a stack of phonograph records. Each surface is coated with a material similar to that on magnetic recording tape, and information is recorded on it. .IP buffer A temporary work space, made available to the user for the duration of a session of text editing and used for creating and modifying the text file. We can think of the buffer as a blackboard that is erased after each class, where each session with the editor is a class. .bp .SH .ce 1 \s+2Session 1\s0 .sp 1 .SH Making contact with \s-1UNIX\s0 .PP To use the editor you must first make contact with the computer by logging in to \s-2UNIX\s0. We'll quickly review the standard \s-2UNIX\s0 login procedure for the two ways you can make contact: on a terminal that is directly linked to the computer, or over a telephone line where the computer answers your call. .SH Directly-linked terminals .PP Turn on your terminal and press the \s-1RETURN\s0 key. You are now ready to log in. .SH Dial-up terminals .PP If your terminal connects with the computer over a telephone line, turn on the terminal, dial the system access number, and, when you hear a high-pitched tone, place the telephone handset in the acoustic coupler, if you are using one. You are now ready to log in. .SH Logging in .PP The message inviting you to log in is: .DS I 1i login: .DE .LP Type your login name, which identifies you to \s-2UNIX\s0, on the same line as the login message, and press \s-2RETURN\s+2. If the terminal you are using has both upper and lower case, .B be sure you enter your login name in lower case; .R otherwise \s-2UNIX\s0 assumes your terminal has only upper case and will not recognize lower case letters you may type. \s-2UNIX\s0 types ``login:'' and you reply with your login name, for example ``susan'': .DS I 1i login: \fBsusan\fR \fI(and press the \s-2RETURN\s0 key)\fR .DE (In the examples, input you would type appears in .B "bold face" to distinguish it from the responses from \s-2UNIX\s0.) .PP \s-2UNIX\s0 will next respond with a request for a password as an additional precaution to prevent unauthorized people from using your account. The password will not appear when you type it, to prevent others from seeing it. The message is: .DS I 1i Password: \fI(type your password and press \s-2RETURN\s+2)\fR .DE If any of the information you gave during the login sequence was mistyped or incorrect, \s-2UNIX\s0 will respond with .DS I 1i Login incorrect login: .DE in which case you should start the login process anew. Assuming that you have successfully logged in, \s-2UNIX\s0 will print the message of the day and eventually will present you with a `%' at the beginning of a fresh line. The `%' is the \s-2UNIX\s0 prompt symbol which tells you that \s-2UNIX\s0 is ready to accept a command. .LP Note: users of ksh(1) will instead be prompted with a `$'. .bd I 3 .SH Asking for \fIex\fP .fl .bd I .PP You are ready to tell \s-2UNIX\s0 that you want to work with .I ex , the text editor. Now is a convenient time to choose a name for the file of text you are about to create. To begin your editing session, type .B ex followed by a space and then the filename you have selected; for example, ``text''. After that, press the \s-2RETURN\s0 key and wait for \fIex\fP's response: .DS I 1i % \fBex text\fP \fI(followed by a \s-2RETURN\s+2)\fR text: new file: line 1 : .DE If you typed the command correctly, you will now be in communication with .I ex . .I Ex has set aside a buffer for use as a temporary working space during your current editing session. Since ``text'' is a new file we are about to create the editor was unable to find that file, which it confirms by saying: .DS I 1i text: new file: line 1 .DE On the next line appears \fIex\fP's prompt `:', announcing that you are in \f2command mode\f1 and .I ex expects a command from you. You may now begin to create the new file. .SH The ``Command not found'' message .PP If you misspelled ex by typing, say, ``ec'', this might appear: .DS I 1i % \fBec\fP ec: Command not found. % .DE Your mistake in calling ex ``ec'' was treated by \s-2UNIX\s0 as a request for a program named ``ec''. Since there is no program named ``ec'', \s-2UNIX\s0 reported that the program was ``not found'' (but be careful, there .I is a program named ``ed''). A new % indicates that \s-2UNIX\s0 is ready for another command, and you may then enter the correct command. .SH A summary .PP Your exchange with \s-2UNIX\s0 as you logged in and made contact with .I ex should look something like this: .DS I 1i login: \fBsusan\fP Password: \&... A Message of General Interest ... % \fBex text\fP text: new file: line 1 : .DE .SH Entering text .PP You may now begin entering text into the buffer. This is done by \fIappending\fP (or adding) text to whatever is currently in the buffer. Since there is nothing in the buffer at the moment, you are appending text to nothing; in effect, since you are adding text to nothing you are creating text. Most edit commands have two equivalent forms: a word that suggests what the command does, and a shorter abbreviation of that word. Many beginners find the full command names easier to remember at first, but once you are familiar with editing you may prefer to type the shorter abbreviations. The command to input text is ``append''. (It may be abbreviated `a'.) Type .B append and press the \s-2RETURN\s0 key. .DS I 1i % \fBex text \fR:\|\fBappend .R .DE .SH .bd I 3 Messages from .I ex .fl .bd I .PP If you make a mistake in entering a command and type something that .I ex does not recognize, it will respond with a message intended to help you diagnose your error. For example, if you misspell the command to input text by typing, perhaps, ``add'' instead of ``append'' or `a', you will receive this message: .DS I 1i :\|\fBadd\fR The add command is unknown : .DE When you receive a diagnostic message, check what you typed in order to determine what part of your command confused .I ex . The message above means that .I ex was unable to recognize your mistyped command and, therefore, did not execute it. Instead, a new `:' appeared to let you know that .I ex is again ready to execute a command. .SH Text input mode .PP By giving the command ``append'' (or using the abbreviation `a'), you entered .I text input mode, .R also known as .I append mode. .R When you enter text input mode, .I ex stops sending you a prompt. You will not receive any prompts or error messages while in text input mode. You can enter pretty much anything you want on the lines. The lines are transmitted one by one to the buffer and held there during the editing session. You may append as much text as you want, and .I when you wish to stop entering text lines you should type a period as the only character on the line and press the \s-2RETURN\s0 key. .R When you type the period and press \s-2RETURN\s0, you signal that you want to stop appending text, and .I ex responds by allowing you to exit text input mode and reenter command mode. .I Ex will again prompt you for a command by printing `:'. .PP Leaving append mode does not destroy the text in the buffer. You have to leave append mode to do any of the other kinds of editing, such as changing, adding, or printing text. If you type a period as the first character and type any other character on the same line, .I ex will believe you want to remain in append mode and will not let you out. As this can be very frustrating, be sure to type .B only the period and the \s-2RETURN\s0 key. .PP This is a good place to learn an important lesson about computers and text: a blank space is a character as far as a computer is concerned. If you so much as type a period followed by a blank (that is, type a period and then the space bar on the keyboard), you will remain in append mode with the last line of text being: .DS I 1i .B .ps +2 \&. .ps -2 .R .DE Let's say that you enter the lines (try to type .B exactly what you see, including ``thiss''): .DS I 1i .B This is some sample text. And thiss is some more text. Text editing is strange, but nice. \&. .R .DE The last line is the period followed by a \s-2RETURN\s0 that gets you out of append mode. .SH Making corrections .PP If you have read a general introduction to \s-2UNIX\s0, you will recall that it is possible to erase individual letters that you have typed. This is done by typing the designated erase character as many times as there are characters you want to erase. .PP The usual erase character varies from place to place and user to user. Often it is the backspace, so you can correct typing errors in the line you are typing by typing the backspace key. (Sometimes it is the DEL key.) If you type the erase character you will notice that the terminal backspaces in the line you are on. You can backspace over your error, and then type what you wanted. .PP If you make a bad start in a line and would like to begin again, you will have to backspace to the beginning of the line, or you can use `^U' to erase everything on the line: .DS I 1i .B Text edtiing is strange, but^U Text editing is strange, but nice. .R .fl .\" .bd S .DE When you type `^U', you erase the entire line typed so far and are given a fresh line to type on. You may immediately begin to retype the line. Additionally, `^W' may be used to delete the last word typed. These methods, unfortunately, do not work after you type the line and press \s-2RETURN\s+2. To make corrections in lines that have been completed, it is necessary to use the editing commands covered in the next sessions. .SH Writing text to disk .PP You are now ready to edit the text. One common operation is to write the text to disk as a file for safekeeping after the session is over. This is the only way to save information from one session to the next, since the editor's buffer is temporary and will last only until the end of the editing session. Learning how to write a file to disk is second in importance only to entering the text. To write the contents of the buffer to a disk file, use the command ``write'' (or its abbreviation `w'): .DS I 1i :\|\fBwrite .R .DE .I Ex will copy the contents of the buffer to a disk file. If the file does not yet exist, a new file will be created automatically and the presence of a ``[new file]'' will be noted. The newly-created file will be given the name specified when you entered the editor, in this case ``text''. To confirm that the disk file has been successfully written, .I ex will repeat the filename and give the number of lines and the total number of characters in the file. The buffer remains unchanged by the ``write'' command. All of the lines that were written to disk will still be in the buffer, should you want to modify or add to them. .PP .I Ex must have a name for the file to be written. If you forgot to indicate the name of the file when you began to edit, a temporary filename will be used. However, if you end your editing session without writing your changes to a non-temporary file, they will be lost. In this case, you can specify the filename in a new write command: .DS I 1i :\|\fBwrite text .R .DE After the ``write'' (or `w'), type a space and then the name of the file. .SH Signing off .PP We have done enough for this first lesson on using the \s-2UNIX\s0 text editor, and are ready to quit the session with edit. To do this we type ``quit'' (or `q') and press \s-2RETURN\s+2: .DS I 1i :\|\fBwrite .R text: new file: 3 lines, 90 characters :\|\fBquit\fR % .DE The % is from \s-2UNIX\s0 to tell you that your session with .I ex is over and you may command \s-2UNIX\s0 further. Since we want to end the entire session at the terminal, we also need to exit from \s-2UNIX\s0. In response to the \s-2UNIX\s0 prompt of ``\|%\|'' type the command .DS I 1i %\|\fBlogout\fR .DE This will end your session with \s-2UNIX\s0, and will ready the terminal for the next user. It is always important to type \fBlogout\fR at the end of a session to make absolutely sure no one could accidentally stumble into your abandoned session and thus gain access to your files, tempting even the most honest of souls. .LP Note: ksh(1) users may have to type .B exit to end their session. .sp 1 .PP This is the end of the first session on \s-2UNIX\s0 text editing. .bp .TL Session 2 .sp .PP Log in with \s-2UNIX\s0 as in the first session: .DS I 1i login: \fBsusan\fP \fI(carriage return)\fR Password: \fI(give password and carriage return)\fR .if t .sp .2v .if n .sp 1 \&... A Message of General Interest ... % .DE When you indicate you want to edit, you can specify the name of the file you worked on last time. This will start .I ex working, and it will fetch the contents of the file into the buffer, so that you can resume editing the same file. When .I ex has copied the file into the buffer, it will repeat its name and report the current line number (generally the last line of the file). Thus, .DS I 1i .B % ex text .R text: unmodified: line 3 : .DE means you asked .I ex to fetch the file named ``text'' for editing, causing it to copy the text into the buffer. .I Ex awaits your further instructions, and indicates this by its prompt character, the colon (:). In this session, we will append more text to our file, print the contents of the buffer, and learn to change the text of a line. .SH Adding more text to the file .PP If you want to add more to the end of your text you may do so by using the .B append command to enter text input mode. When ``append'' is the first command of your editing session, the lines you enter are placed at the end of the buffer. Here we'll use the abbreviation for the append command, `a': .DS I 1i :\|\fBa This is text added in Session 2. It doesn't mean much here, but it does illustrate the editor. \|\fB\s+2\&.\s-2 .R .DE You may recall that once you enter append mode using the `a' (or ``append'') command, you need to type a line containing only a period (.) to exit append mode. .SH Interrupt .PP Should you press the `^C' key while working with .I ex , it will send this message to you: .DS I 1i Interrupted : .DE Any command that .I ex might be executing is terminated by `^C', causing .I ex to prompt you for a new command. If you are appending text at the time, you will exit from append mode and be expected to give another command. The line of text you were typing when the append command was interrupted will not be entered into the buffer. .SH Making corrections .PP If while typing the line you hit an incorrect key, recall that you may delete the incorrect character or cancel the entire line of input by erasing in the usual way. Refer either to the last few pages of Session 1 if you need to review the procedures for making a correction. The most important idea to remember is that erasing a character or cancelling a line must be done before you press the \s-2RETURN\s+2 key. .SH Listing what's in the buffer (p) .PP Having appended text to what you wrote in Session 1, you might want to see all the lines in the buffer. To print the contents of the buffer, type the command: .DS I 1i :\|\fB1,$p .R .DE The `1' .\" .FS .\" *The numeral ``one'' is the top left-most key, .\" and should not be confused with the letter ``el''. .\" .FE stands for line 1 of the buffer; the `$' is a special symbol designating the last line of the buffer; and `p' is the print command. Thus this command prints from line 1 to the end of the buffer. The command ``1,$p'' gives you: .DS I 1i This is some sample text. And thiss is some more text. Text editing is strange, but nice. This is text added in Session 2. It doesn't mean much here, but it does illustrate the editor. .DE .PP Additionally, the percentage symbol (`%') may be used as a shorthand for `1,$'. Thus the commands `%p' and `1,$p' are identical. .PP Occasionally, you may accidentally type a character that can't be printed, which can be done by striking a key while the \s-2CTRL\s0 key is pressed. In printing lines, .I ex uses a special notation to show the existence of non-printing characters. Suppose you had introduced the non-printing character ``control-A'' into the word ``illustrate'' by accidentally pressing the \s-2CTRL\s0 key while typing `a'. This can happen on many terminals because the \s-2CTRL\s+2 key and the `A' key are beside each other. If your finger presses between the two keys, control-A results. When asked to print the contents of the buffer, edit would display .DS I 1i it does illustr^Ate the editor. .DE To represent the control-A, .I ex shows `^A'. The sequence `^' followed by a capital letter stands for the one character entered by holding down the \s-2CTRL\s0 key and typing the letter which appears after the `^'. We'll soon discuss the commands that can be used to correct this typing error. .PP In looking over the text we see that ``this'' is typed as ``thiss'' in the second line, a deliberate error so we can learn to make corrections. Let's correct the spelling. .SH Finding things in the buffer .PP In order to change something in the buffer we first need to find it. We can find ``thiss'' in the text we have entered by looking at a listing of the lines. Physically speaking, we search the lines of text looking for ``thiss'' and stop searching when we have found it. The way to tell .I ex to search for something is to type it inside slash marks: .DS I 1i :\|\fB/thiss/ .R .DE By typing .B /thiss/ and pressing \s-1RETURN\s0, you instruct .I ex to search for ``thiss''. If you ask .I ex to look for a pattern of characters which it cannot find in the buffer, it will respond ``Pattern not found''. When .I ex finds the characters ``thiss'', it will print the line of text for your inspection: .DS I 1i And thiss is some more text. .DE .I Ex is now positioned in the buffer at the line it just printed, ready to make a change in the line. .bp .SH The current line .PP .I Ex keeps track of the line in the buffer where it is located at all times during an editing session. In general, the line that has been most recently printed, entered, or changed is the current location in the buffer. The editor is prepared to make changes at the current location in the buffer, unless you direct it to another location. .PP In particular, when you bring a file into the buffer, you will be located at the last line in the file, where the editor left off copying the lines from the file to the buffer. If your first editing command is ``append'', the lines you enter are added to the end of the file, after the current line \(em the last line in the file. .PP You can refer to your current location in the buffer by the symbol period (.) usually known by the name ``dot''. If you type `.' and carriage return you will be instructing .I ex to print the current line: .DS I 1i :\|\fB\s+2\&.\s-2 .R And thiss is some more text. .DE .PP If you want to know the number of the current line, you can type .B \&.= and press \s-2RETURN\s+2, and .I ex will respond with the line number: .DS I 1i :\|\fB\s+2.\s-2= .R 2 .DE If you type the number of any line and press \s-2RETURN\s+2, .I ex will position you at that line and print its contents: .DS I 1i :\|\fB2 .R And thiss is some more text. .DE You should experiment with these commands to gain experience in using them to make changes. .SH Numbering lines (nu) .PP The .B number (nu) .R command is similar to print, giving both the number and the text of each printed line. To see the number and the text of the current line type .DS I 1i :\|\fBnu .R \0\0\0\0\02\0\0And thiss is some more text. .DE Note that the shortest abbreviation for the number command is ``nu'' (and not `n', which is used for a different command). You may specify a range of lines to be listed by the number command in the same way that lines are specified for print. For example, \f31,$nu\f1 lists all lines in the buffer with their corresponding line numbers. .SH Substitute command (s) .PP Now that you have found the misspelled word, you can change it from ``thiss'' to ``this''. As far as .I ex is concerned, changing things is a matter of substituting one thing for another. As .I a stood for .I append , so .I s stands for .I substitute . We will use the abbreviation `s' to reduce the chance of mistyping the substitute command. This command will instruct .I ex to make the change: .DS I 1i \f32s/thiss/this/\f1 .DE We first indicate the line to be changed, line 2, and then type an `s' to indicate we want .I ex to make a substitution. Inside the first set of slashes are the characters that we want to change, followed by the characters to replace them, and then a closing slash mark. To summarize: .DS I 1i 2s/ \fIwhat is to be changed\fR / \fIwhat to change it to \fR/ .DE If .I ex finds an exact match of the characters to be changed it will make the change .B only in the first occurrence of the characters. If it does not find the characters to be changed, it will respond: .DS I 1i No match found .DE indicating that your instructions could not be carried out. When .I ex does find the characters that you want to change, it will make the substitution and automatically print the changed line, so that you can check that the correct substitution was made. In the example, .DS I 1i :\|\fB2s/thiss/this/ .R And this is some more text. .DE line 2 (and line 2 only) will be searched for the characters ``thiss'', and when the first exact match is found, ``thiss'' will be changed to ``this''. Strictly speaking, it was not necessary above to specify the number of the line to be changed. In .DS I 1i :\|\fBs/thiss/this/ .R .DE .I ex will assume that we mean to change the line where we are currently located (`.'). In this case, the command without a line number would have produced the same result because we were already located at the line we wished to change. .PP For another illustration of the substitute command, let us choose the line: .DS I 1i Text editing is strange, but nice. .DE You can make this line a bit more positive by taking out the characters ``strange, but\ '' so the line reads: .DS I 1i Text editing is nice. .DE A command that will first position .I ex at the desired line and then make the substitution is: .DS I 1i :\|\fB/strange/s/strange, but // .R .DE .LP What we have done here is combine our search with our substitution. Such combinations are perfectly legal, and speed up editing quite a bit once you get used to them. That is, you do not necessarily have to use line numbers to identify a line to .I ex . Instead, you may identify the line you want to change by asking .I ex to search for a specified pattern of letters that occurs in that line. The parts of the above command are: .TS l l. \fB/strange/\fP tells \fIex\fP to find the characters ``strange'' in the text \fBs\fP tells \fIex\fP to make a substitution \fB/strange, but //\fP substitutes nothing at all for the characters ``strange, but '' .TE .PP You should note the space after ``but'' in ``/strange, but /''. If you do not indicate that the space is to be taken out, your line will read: .DS I 1i .if t Text editing is nice. .if n Text editing is nice. .DE which looks a little funny because of the extra space between ``is'' and ``nice''. Again, we realize from this that a blank space is a real character to a computer, and in editing text we need to be aware of spaces within a line just as we would be aware of an `a' or a `4'. .SH Another way to list what's in the buffer (z) .PP Although the print command is useful for looking at specific lines in the buffer, other commands may be more convenient for viewing large sections of text. You can ask to see a screen full of text at a time by using the command .B z. If you type .DS I 1i :\|\fB1z .R .DE .I ex will start with line 1 and continue printing lines, stopping either when the screen of your terminal is full or when the last line in the buffer has been printed. If you want to read the next segment of text, type the command .DS I 1i :\|\fBz .DE If no starting line number is given for the z command, printing will start at the ``current'' line, in this case the last line printed. Viewing lines in the buffer one screen full at a time is known as \fIpaging\fR. Paging can also be used to print a section of text on a hard-copy terminal. .SH Saving the modified text .PP This seems to be a good place to pause in our work, and so we should end the second session. If you (in haste) type `q' to quit the session your dialogue with .I ex will be: .DS :\|\fBq .R File may be modified since last complete write; write or use ! to override : .DE This is \fIex\fP's warning that you have not written the modified contents of the buffer to disk. You run the risk of losing the work you did during the editing session since you typed the latest write command. Because in this lesson we have not written to disk at all, everything we have done would have been lost if .I ex had obeyed the \fBq\fR command. If you did not want to save the work done during this editing session, you would have to type ``q!'' or (``quit!'') to confirm that you indeed wanted to end the session immediately, leaving the file as it was after the most recent ``write'' command. However, since you want to save what you have edited, you need to type: .DS I 1i :\|\fBw .R text: 6 lines, 171 characters .DE and then follow with the commands to quit and logout: .DS I 1i :\|\fBq % \fBlogout\fR .DE and hang up the phone or turn off the terminal when \s-2UNIX\s0 asks for a name. Terminals connected to the port selector will stop after the logout command, and pressing keys on the keyboard will do nothing. .sp 1 .PP This is the end of the second session on \s-2UNIX\s0 text editing. .bp .TL Session 3 .SH Bringing text into the buffer (e) .PP Log in to \s-2UNIX\s0 and make contact with .I ex . You should try to log in without looking at the notes, but if you must then by all means do. .PP Did you remember to give the name of the file you wanted to edit? That is, did you type .DS I 1i % \fBex text\fR .DE or simply .DS I 1i % \fBex\fR .DE Both ways get you in contact with .I ex , but the first way will bring a copy of the file named ``text'' into the buffer. If you did forget to tell .I ex the name of your file, you can get it into the buffer by typing: .DS I 1i :\|\fBe text .R text: unmodified: line 6 .DE The command .B edit , which may be abbreviated \fBe\fR, tells .I ex that you want to erase anything that might already be in the buffer and bring a copy of the file ``text'' into the buffer for editing. You may also use the edit (e) command to change files in the middle of an editing session, or to give .I ex the name of a new file that you want to create. Because the .B edit command clears the buffer, you will receive a warning if you try to edit a new file without having saved a copy of the old file. This gives you a chance to write the contents of the buffer to disk before editing the next file. .SH Moving text in the buffer (m) .PP .I Ex allows you to move lines of text from one location in the buffer to another by means of the .B move (\fBm\fR) command. The first two examples are for illustration only, though after you have read this Session you are welcome to return to them for practice. The command .DS I 1i :\|\fB2,4m$ .R .DE directs .I ex to move lines 2, 3, and 4 to the end of the buffer ($). The format for the move command is that you specify the first line to be moved, the last line to be moved, the move command `m', and the line after which the moved text is to be placed. So, .DS I 1i :\|\fB1,3m6 .R .DE would instruct .I ex to move lines 1 through 3 (inclusive) to a location after line 6 in the buffer. To move only one line, say, line 4, to a location in the buffer after line 5, the command would be ``4m5''. .PP Let's move some text using the command: .DS I 1i :\|\fB5,$m1 .R it does illustrate the editor. .DE After executing the .B move command, .I ex prints the last moved line for your inspection. If you want to see more than just the last line, you can then use the .B print (\fBp\fP), .B z , or .B number (\fBnu\fP) commands to view more text. The buffer should now contain: .DS I 1i This is some sample text. It doesn't mean much here, but it does illustrate the editor. And this is some more text. Text editing is nice. This is text added in Session 2. .DE You can restore the original order by typing: .DS I 1i :\|\fB4,$m1 .R .DE or, combining context searching and the move command: .DS I 1i :\|\fB/And this is some/,/This is text/m/This is some sample/ .R .DE (Do not type both examples here!) The problem with combining context searching with the move command is that your chance of making a typing error in such a long command is greater than if you type line numbers. .SH Copying lines (copy) .PP The .B copy command is used to make a second copy of specified lines, leaving the original lines where they were. Copy has the same format as the move command, for example: .DS I 1i :\|\fB2,5copy $ .R .DE makes a copy of lines 2 through 5, placing the added lines after the buffer's end ($). Experiment with the copy command so that you can become familiar with how it works. Note that the shortest abbreviation for copy is \f3co\f1 (and not the letter `c', which has another meaning). .SH Deleting lines (d) .PP Suppose you want to delete the line .DS I 1i This is text added in Session 2. .DE from the buffer. If you know the number of the line to be deleted, you can type that number followed by \fBdelete\fR or \fBd\fR. This example deletes line 4, which is ``This is text added in Session 2.'' if you typed the commands suggested so far. .DS I 1i :\|\fB4d .R It doesn't mean much here, but .DE Here `4' is the number of the line to be deleted, and ``delete'' or `d' is the command to delete the line. After executing the delete command, .I ex prints the line that has become the current line (`.'). .PP If you do not happen to know the line number you can search for the line and then delete it using this sequence of commands: .DS I 1i :\|\fB/added in Session 2./ .R This is text added in Session 2. :\|\fBd .R It doesn't mean much here, but .DE The ``/added in Session 2./'' asks .I ex to locate and print the line containing the indicated text, starting its search at the current line and moving line by line until it finds the text. Once you are sure that you have correctly specified the line you want to delete, you can enter the .B delete (\fBd\fP) command. In this case it is not necessary to specify a line number before the `d'. If no line number is given, .I ex deletes the current line (`.'), that is, the line found by our search. After the deletion, your buffer should contain: .DS I 1i This is some sample text. And this is some more text. Text editing is nice. It doesn't mean much here, but it does illustrate the editor. And this is some more text. Text editing is nice. This is text added in Session 2. It doesn't mean much here, but .DE To delete both lines 2 and 3: .DS I 1i And this is some more text. Text editing is nice. .DE you type .DS I 1i :\|\f32,3d\f1 .DE which specifies the range of lines from 2 to 3, and the operation on those lines \(em `d' for delete. .PP The previous example assumes that you know the line numbers for the lines to be deleted. If you do not you might combine the search command with the delete command: .DS I 1i :\|\fB/And this is some/,/Text editing is nice./d .R .DE .SH A word or two of caution .PP In using the search function to locate lines to be deleted you should be .B absolutely sure .R the characters you give as the basis for the search will take .I ex to the line you want deleted. .I Ex will search for the first occurrence of the characters starting from where you last edited \- that is, from the line you see printed if you type dot (.). .PP A search based on too few characters may result in the wrong lines being deleted, which .I ex will do as easily as if you had meant it. For this reason, it is usually safer to specify the search and then delete in two separate steps, at least until you become familiar enough with using the editor that you understand how best to specify searches. For a beginner it is not a bad idea to double-check each command before pressing \s-2RETURN\s+2 to send the command on its way. .SH Undo (u) to the rescue .PP The .B undo (\fBu\fP) command has the ability to reverse the effects of the last command that changed the buffer. To undo the previous command, type `u' or ``undo''. Undo can rescue the contents of the buffer from many an unfortunate mistake. However, its powers are not unlimited, so it is still wise to be reasonably careful about the commands you give. .PP It is possible to undo only commands which have the power to change the buffer \(em for example, .B delete , .B append , .B move , .B copy , .B substitute , and even .B undo itself. The commands .B write (\fBw\fP) and .B edit (\fBe\fP), which interact with disk files, cannot be undone, nor can commands that do not change the buffer, such as print. Most importantly, the .B only command that can be reversed by undo is the last ``undo-able'' command you typed. .PP To illustrate, let's issue an .B undo command. Recall that the last buffer-changing command we gave deleted the lines formerly numbered 2 and 3. Typing .B undo at this moment will reverse the effects of the deletion, causing those two lines to be replaced in the buffer. .DS I 1i :\|\fBu .R And this is some more text. .DE Here again, .I ex prints the text of the line which is now ``dot'' (the current line). .SH More about the dot (.) and buffer end ($) .PP The function assumed by the symbol dot depends on its context. It can be used: .IP 1. to exit from append mode; we type dot (and only a dot) on a line and press \s-2RETURN\s+2; .IP 2. to refer to the line we are at in the buffer. .LP Dot can also be combined with the equal sign to get the number of the line currently being edited: .DS I 1i :\|\fB\&.= .R .DE If we type `\fB.\fR=' we are asking for the number of the line, and if we type `\fB.\fR' we are asking for the text of the line. .PP In this editing session and the last, we used the dollar sign to indicate the end of the buffer in commands such as .B print , .B copy , and .B move . The dollar sign as a command asks edit to print the last line in the buffer. If the dollar sign is combined with the equal sign (\f3$=\f1) edit will print the line number corresponding to the last line in the buffer. .PP `\fB.\fR' and `$', then, represent line numbers. Whenever appropriate, these symbols can be used in place of line numbers in commands. For example .DS I 1i :\|\fB\s+2.\s-2,$d .R .DE instructs edit to delete all lines from the current line (\fB.\fR) to the end of the buffer. .SH Moving around in the buffer (+ and \-) .PP When you are editing you often want to go back and re-read a previous line. You could specify a context search for a line you want to read if you remember some of its text, but if you simply want to see what was written a few, say 3, lines ago, you can type .DS I 1i .B \-3p .DE This tells .I ex to move back to a position 3 lines before the current line (.) and print that line. You can move forward in the buffer similarly: .DS I 1i .B +2p .DE instructs .I ex to print the line that is 2 ahead of your current position. .PP You may use `+' and `\-' in any command where edit accepts line numbers. Line numbers specified with `+' or `\-' can be combined to print a range of lines. The command .DS I 1i :\|\fB\-1,+2copy$ .R .DE makes a copy of 4 lines: the current line, the line before it, and the two after it. The copied lines will be placed after the last line in the buffer ($), and the original lines referred to by `\-1' and `+2' remain where they are. .PP Try typing only `\-'; you will move back one line just as if you had typed `\-1p'. Typing the command `+' works similarly. You might also try typing a few plus or minus signs in a row (such as ``+++'') to see \fIex\fP's response. Typing \s-2RETURN\s+2 alone on a line is the equivalent of typing ``+1p''; it will move you one line ahead in the buffer and print that line. .PP If you are at the last line of the buffer and try to move further ahead, perhaps by typing a `+' or a carriage return alone on the line, .I ex will remind you that you are at the end of the buffer: .sp .nf .ti 1i Illegal address: only 6 lines in the file .fi .LP Similarly, if you try to move to a position before the first line, .I ex will print a message similar to: .sp .nf .ti 1i The print command doesn't permit an address of 0 .fi .LP The number associated with a buffer line is the line's ``address'', in that it can be used to locate the line. .SH Changing lines (c) .PP You can also delete certain lines and insert new text in their place. This can be accomplished easily with the .B change (\fBc\fP) command. The change command instructs .I ex to delete specified lines and then switch to text input mode to accept the text that will replace them. Let's say you want to change the first two lines in the buffer: .DS I 1i This is some sample text. And this is some more text. .DE to read .DS I 1i This text was created with the \s-2UNIX\s0 text editor. .DE To do so, you type: .DS I 1i :\|\fB1,2c This text was created with the \s-2UNIX\s0 text editor. \s+2\&.\s-2 .R : .DE In the command .B 1,2c we specify that we want to change the range of lines beginning with 1 and ending with 2 by giving line numbers as with the .B print command. These lines will be deleted. Any text typed on the following lines will be inserted into the position where lines were deleted by the change command. .B You will remain in text input mode until you exit in the usual way, by typing a period alone on a line. .R Note that the number of lines added to the buffer need not be the same as the number of lines deleted. .sp 1 .PP This is the end of the third session on text editing with \s-2UNIX\s0. .bp .SH .ce 1 \s+2Session 4\s0 .sp .PP This lesson covers several topics, starting with commands that apply throughout the buffer, characters with special meanings, and how to issue \s-2UNIX\s0 commands while in the editor. The next topics deal with files: more on reading and writing, and methods of recovering files lost in a crash. The final section suggests sources of further information. .SH Making commands global (g) .PP One disadvantage to the commands we have used for searching or substituting is that if you have a number of instances of a word to change it appears that you have to type the command repeatedly, once for each time the change needs to be made. .I Ex however, provides a way to make commands apply to the entire contents of the buffer \- the .B global (\fBg\fP) command. .PP To print all lines containing a certain sequence of characters (say, ``text'') the command is: .DS I 1i :\|\fBg/text/p .R .DE The `g' instructs .I ex to make a global search for all lines in the buffer containing the characters ``text''. The `p' prints the lines found. .PP To issue a global command, start by typing a `g' and then a search pattern identifying the lines to be affected. Then, on the same line, type the command to be executed for the identified lines. Global substitutions are frequently useful. For example, to change all instances of the word ``text'' to the word ``material'' the command would be a combination of the global search and the substitute command: .DS I 1i :\|\fBg/text/s/text/material/g .R .DE Note the `g' at the end of the global command, which instructs edit to change each and every instance of ``text'' to ``material''. If you do not type the `g' at the end of the command only the .I first instance of ``text'' \fIin each line\fR will be changed (the normal result of the substitute command). The `g' at the end of the command is independent of the `g' at the beginning. You may give a command such as: .DS I 1i :\|\fB5s/text/material/g .R .DE to change every instance of ``text'' in line 5 alone. Further, neither command will change ``text'' to ``material'' if ``Text'' begins with a capital rather than a lower-case `t'. .PP .I Ex does not automatically print the lines modified by a global command. If you want the lines to be printed, type a `p' at the end of the global command: .DS I 1i :\|\fBg/text/s/text/material/gp .R .DE You should be careful about using the global command in combination with any other \- in essence, be sure of what you are telling edit to do to the entire buffer. For example, .DS I 1i :\|\fBg/ /d .DE will delete every line containing a blank anywhere in it. This could adversely affect your document, since most lines have spaces between words and thus would be deleted. Fortunately, the .B undo command can reverse the effects of a .B global command. You should experiment with the .B global command on a small file of text to see what it can do for you. .SH More about searching and substituting .PP In using slashes to identify a character string that we want to search for or change, we have always specified the exact characters. There is a less tedious way to repeat the same string of characters. To change ``text'' to ``texts'' we may type either .DS I 1i :\|\fB/text/s/text/texts/ .R .DE as we have done in the past, or a somewhat abbreviated command: .DS I 1i :\|\fB/text/s//texts/ .R .DE In this example, the characters to be changed are not specified \- there are no characters, not even a space, between the two slash marks that indicate what is to be changed. This lack of characters between the slashes is taken by the editor to mean ``use the characters we last searched for as the characters to be changed.'' .PP Similarly, the last context search may be repeated by typing a pair of slashes with nothing between them: .DS I 1i :\|\fB/does/ .R It doesn't mean much here, but :\|\fB// .R it does illustrate the editor. .DE (You should note that the search command found the characters ``does'' in the word ``doesn't'' in the first search request.) Because no characters are specified for the second search, the editor scans the buffer for the next occurrence of the characters ``does''. .PP .I Ex normally searches forward through the buffer, wrapping around from the end of the buffer to the beginning, until the specified character string is found. If you want to search in the reverse direction, use question marks (`?') instead of slashes to surround the characters you are searching for. .PP It is also possible to repeat the last substitution without having to retype the entire command. An ampersand (`&') used as a command repeats the most recent substitute command, using the same search and replacement patterns. After altering the current line by typing .DS I 1i :\|\fBs/text/texts/ .R .DE you type .DS I 1i :\|\fB/text/& .R .DE or simply .DS I 1i :\|\fB//& .R .DE to make the same change on the next line in the buffer containing the characters ``text''. .SH Special characters .PP Some characters have special meanings when used in specifying searches. `$' is taken by the editor to mean ``end of the line'' and is used to identify strings that occur at the end of a line. .DS I 1i :\|\fBg/text.$/s//material./p .R .DE tells the editor to search for all lines ending in ``text.'' (and nothing else, not even a blank space), to change each final ``text.'' to ``material.'', and print the changed lines. .PP The symbol `^' indicates the beginning of a line. Thus, .DS I 1i :\|\fBs/^/1. / .R .DE instructs the editor to insert ``1.'' and a space at the beginning of the current line. .PP The characters `$' and `^' have special meanings only in the context of searching. At other times, they are ordinary characters. If you ever need to search for a character that has a special meaning, you must indicate that the character is to lose temporarily its special significance by typing another special character, the backslash (\\), before it. .DS I 1i :\|\fBs/\\\\\&$/dollar/ .R .DE looks for the character `$' in the current line and replaces it by the word ``dollar''. Were it not for the backslash, the `$' would have represented ``the end of the line'' in your search rather than the character `$'. The backslash retains its special significance unless it is preceded by another backslash. .LP For a complete list of special characters, see the "Ex Reference Manual", /usr/share/doc/usd/13.ex/. .SH Issuing \s-2UNIX\s0 commands from the editor .PP After creating several files with the editor, you may want to delete files no longer useful to you or ask for a list of your files. Removing and listing files are not functions of the editor, and so they require the use of \s-2UNIX\s0 system commands (also referred to as ``shell'' commands, as ``shell'' is the name of the program that processes \s-2UNIX\s0 commands). You do not need to quit the editor to execute a \s-2UNIX\s0 command as long as you indicate that it is to be sent to the shell for execution. To use the \s-2UNIX\s0 command .B rm to remove the file named ``junk'' type: .DS I 1i :\|\fB!rm junk .R ! : .DE The exclamation mark (`!') indicates that the rest of the line is to be processed as a shell command. If the buffer contents have not been written since the last change, a warning will be printed before the command is executed: .DS I 1i File may be modified since last write. .DE The editor prints a `!' when the command is completed. Other tutorials describe useful features of the system, of which an editor is only one part. .SH Filenames and file manipulation .PP Throughout each editing session, .I ex keeps track of the name of the file being edited as the .I "current filename" . .I Ex remembers as the current filename the name given when you entered the editor. The current filename changes whenever the .B edit (\fBe\fP) command is used to specify a new file. Once .I ex has recorded a current filename, it inserts that name into any command where a filename has been omitted. If a write command does not specify a file, .I ex as we have seen, supplies the current filename. If you are editing a file named ``draft3'' having 283 lines in it, you can have the editor write onto a different file by including its name in the write command: .DS I 1i :\fB\|w chapter3 .R chapter3: new file: 283 lines, 8698 characters .DE The current filename remembered by the editor .I will not be changed as a result of the write command. .R Thus, if the next write command does not specify a name, edit will write onto the current file (``draft3'') and not onto the file ``chapter3''. .SH The file (f) command .PP To ask for the current filename, type .B file (or .B f ). In response, the editor provides current information about the buffer, including the filename, your current position, the number of lines in the buffer, and the percent of the distance through the file your current location is. .DS I 1i :\|\fBf .R text: modified: line 3 of 4 [75%] .DE .\"The expression ``[Edited]'' indicates that the buffer contains .\"either the editor's copy of the existing file ``text'' .\"or a file which you are just now creating. If the contents of the buffer have changed since the last time the file was written, the editor will tell you that the file has been ``modified:''. After you save the changes by writing onto a disk file, the buffer will no longer be considered modified: .DS I 1i :\|\fBw .R text: 4 lines, 88 characters :\|\fBf .R text: unmodified: line 3 of 4 [75%] .DE .SH Reading additional files (r) .PP The .B read (\fBr\fP) command allows you to add the contents of a file to the buffer at a specified location, essentially copying new lines between two existing lines. To use it, specify the line after which the new text will be placed, the .B read (\fBr\fP) command, and then the name of the file. If you have a file named ``example'', the command .DS I 1i :\|\fB$r example .R example: 18 lines, 473 characters .DE reads the file ``example'' and adds it to the buffer after the last line. The current filename is not changed by the read command. .SH Writing parts of the buffer .PP The .B write (\fBw\fP) .R command can write all or part of the buffer to a file you specify. We are already familiar with writing the entire contents of the buffer to a disk file. To write only part of the buffer onto a file, indicate the beginning and ending lines before the write command, for example .DS I 1i :\|\fB45,$w ending .R .DE Here all lines from 45 through the end of the buffer are written onto the file named .I ending . The lines remain in the buffer as part of the document you are editing, and you may continue to edit the entire buffer. Your original file is unaffected by your command to write part of the buffer to another file. Edit still remembers whether you have saved changes to the buffer in your original file or not. .SH Recovering files .PP Although it does not happen very often, there are times \s-2UNIX\s+2 stops working because of some malfunction. This situation is known as a \fIcrash\fR. Under most circumstances, .I ex 's crash recovery feature is able to save work to within a few lines of changes before a crash (or a remote connection timeout). If you lose the contents of an editing buffer in a system crash, you will normally receive mail when you log in that gives the name of the recovered file. To recover the file, enter the editor and type the command .B recover (\fBrec\fR), followed by the name of the lost file. For example, to recover the buffer for an editing session involving the file ``chap6'', the command is: .DS I 1i .R :\|\fBrecover chap6 .R .DE Recover is sometimes unable to save the entire buffer successfully, so always check the contents of the saved buffer carefully before writing it back onto the original file. For best results, write the buffer to a new file temporarily so you can examine it without risk to the original file. Unfortunately, you cannot use the recover command to retrieve a file you removed using the shell command \f3rm\f1. .SH Other recovery techniques .PP If something goes wrong when you are using the editor, it may be possible to save your work by using the command .B preserve (\fBpre\fR), which saves the buffer as if the system had crashed. If you are writing a file and you get the message ``Quota exceeded'', you have tried to use more disk storage than is allotted to your account. .I Proceed with caution .R because it is likely that only a part of the editor's buffer is now present in the file you tried to write. In this case you should use the shell escape from the editor (!) to remove some files you don't need and try to write the file again. If this is not possible and you cannot find someone to help you, enter the command .DS I 1i :\|\fBpreserve .R .DE and wait for the reply, .DS I 1i File preserved .DE If you do not receive this reply, seek help immediately. Do not simply leave the editor. If you do, the buffer will be lost, and you may not be able to save your file. If the reply is ``File preserved'' you can leave the editor (or log out) to remedy the situation. After a .B preserve , you can use the .B recover command once the problem has been corrected, or the \fB\-r\fR option of the .I ex command if you leave the editor and want to return. .PP If you make an undesirable change to the buffer and type a write command before discovering your mistake, the modified version will replace any previous version of the file. Should you ever lose a good version of a document in this way, do not panic and leave the editor. As long as you stay in the editor, the contents of the buffer remain accessible. Depending on the nature of the problem, it may be possible to restore the buffer to a more complete state with the .B undo command. After fixing the damaged buffer, you can again write the file to disk. .SH Options, set, and editor startup files .PP The editor has a set of options, some of which have been mentioned above. The most useful options are given in the following table. .PP The options are of three kinds: numeric options, string options, and toggle options. You can set numeric and string options by a statement of the form .DS \fBset\fR \fIopt\fR\fB=\fR\fIval\fR .DE and toggle options can be set or unset by statements of one of the forms .DS \fBset\fR \fIopt\fR \fBset\fR \fBno\fR\fIopt\fR .DE .KF .TS lb lb lb lb l l l a. Name Default Description _ autoindent noai Supply indentation automatically autowrite noaw Automatic write before \fB:n\fR, \fB:ta\fR, \fB^^\fR, \fB!\fR ignorecase noic Ignore case in searching .\" lisp nolisp \fB( { ) }\fR commands deal with S-expressions list nolist Tabs print as ^I; end of lines marked $ magic magic . [ and * are special in scans number nonu Lines prefixed with line numbers report report=5 Number of line which editor reports after changes. shiftwidth sw=8 Shift distance for <, > and \fB^D\fP and \fB^T\fR term $TERM The kind of terminal you are using. .TE .KE These statements can be placed in your EXINIT in your environment, or given while you are running .I ex by typing them at the \fB:\fR prompt and following them with a \s-2CR\s0. .PP You can get a list of all options which you have changed by the command \fB:set\fR\s-2CR\s0, or the value of a single option by the command \fB:set\fR \fIopt\fR\fB?\fR\s-2CR\s0. A list of all possible options and their values is generated by \fB:set all\fP\s-2CR\s0. Set can be abbreviated \fBse\fP. Multiple options can be placed on one line, e.g. \fB:se ai aw nu\fP\s-2CR\s0. .PP Options set by the \fBset\fP command only last while you stay in the editor. It is common to want to have certain options set whenever you use the editor. This can be accomplished by creating a list of commands which are to be run every time you start up \fIex\fP. For example: .DS \fBset\fP ai ic report=1 .DE sets the options \fIautoindent\fP, \fIignorecase\fP, and \fIreport\fP (to the value of 1). This string should be placed in the variable EXINIT in your environment. If you use the shell \fIcsh\fP, put this line in the file .I .login in your home directory: .DS I setenv EXINIT 'set ai ic report=1' .DE If you use the standard shell \fIsh\fP, put these lines in the file .I .profile in your home directory: .DS export EXINIT='set ai ic report=1' .DE Of course, the particulars of the line would depend on which options you wanted to set. .SH Further reading and other information .PP These lessons are intended to introduce you to the editor and its more commonly-used commands. We have not covered all of the editor's commands, but a selection of commands that should be sufficient to accomplish most of your editing tasks. You can find out more about the editor in the .I Ex Reference Manual. .R One way to become familiar with the manual is to begin by reading the description of commands that you already know. .bd I 3 .SH .ce 1 \s+2Index\s0 .LP .sp 2 .2C .nf addressing, \fIsee\fR line numbers ampersand, 20 append mode, 6-7 append (a) command, 6, 7, 9 ``At end of file'' (message), 18 backslash (\\), 21 buffer, 3 caret (^), 10, 20 change (c) command, 18 command mode, 5-6 ``Command not found'' (message), 6 context search, 10-12, 19-21 control characters (`^' notation), 10 control-H, 7 copy (co) command, 15 corrections, 7, 16 current filename, 21 current line (\|.\|), 11, 17 delete (d) command, 15-16 dial-up, 5 disk, 3 documentation, 3, 23 dollar ($), 10, 11, 17, 20-21 dot (\f3\|.\|\f1) 11, 17 edit (text editor), 3, 5, 23 edit (e) command, 5, 9, 14 editing commands: .in +.25i append (a), 6, 7, 9 change (c), 18 copy (co), 15 delete (d), 15-16 edit (text editor), 3, 5, 23 edit (e), 5, 9, 14 file (f), 21-22 global (g), 19 move (m), 14-15 number (nu), 11 preserve (pre), 22-23 print (p), 10 quit (q), 8, 13 read (r), 22 recover (rec), 22, 23 substitute (s), 11-12, 19, 20 undo (u), 16-17, 23 write (w), 8, 13, 21, 22 z, 12-13 ! (shell escape), 21 $=, 17 +, 17 \-, 17 //, 12, 20 ??, 20 \&., 11, 17 \&.=, 11, 17 .in -.25i entering text, 3, 6-7 erasing .in +.25i characters (^H), 7 lines (@), 7 .in -.25i error corrections, 7, 16 ex (text editor), 23 \fIEx Reference Manual\fR, 23 exclamation (!), 21 file, 3 file (f) command, 21-22 file recovery, 22-23 filename, 3, 21 global (g) command, 19 input mode, 6-7 Interrupt (message), 9 line numbers, \fIsee also\fR current line .in +.25i dollar sign ($), 10, 11, 17 dot (\|.\|), 11, 17 relative (+ and \-), 17 .in -.25i list, 10 logging in, 4-6 logging out, 8 ``Login incorrect'' (message), 5 minus (\-), 17 move (m) command, 14-15 ``Negative address\(emfirst buffer line is 1'' (message), 18 ``No current filename'' (message), 8 ``No such file or directory'' (message), 5, 6 ``No write since last change'' (message), 21 non-printing characters, 10 ``Nonzero address required'' (message), 18 ``Not an editor command'' (message), 6 ``Not that many lines in buffer'' (message), 18 number (nu) command, 11 options, 22 password, 5 period (\|.\|), 11, 17 plus (+), 17 preserve (pre) command, 22-23 print (p) command, 10 program, 3 prompts .in .25i % (\s-2UNIX\s0), 5 : (edit), 5, 6, 7 \0 (append), 7 .in -.25i question (?), 20 quit (q) command, 8, 13 read (r) command, 22 recover (rec) command, 22, 23 recovery, \fIsee\fR\| file recovery references, 3, 23 remove (rm) command, 21, 22 reverse command effects (undo), 16-17, 23 searching, 10-12, 19-21 shell, 21 shell escape (!), 21 slash (/), 11-12, 20 special characters (^, $, \\), 10, 11, 17, 20-21 substitute (s) command, 11-12, 19, 20 terminals, 4-5 text input mode, 7 undo (u) command, 16-17, 23 \s-1UNIX\s0, 3 write (w) command, 8, 13, 21, 22 z command, 12-13 ================================================ FILE: docs/USD.doc/exref/ex.rm ================================================ .\" $OpenBSD: ex.rm,v 1.9 2022/12/26 19:16:03 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)ex.rm 8.5 (Berkeley) 8/18/96 .\" .if n \{\ .po 5n .ll 70n .\} .nr LL 6.5i .nr FL 6.5i .EH 'USD:12-%''Ex Reference Manual' .OH 'Ex Reference Manual''USD:12-%' .nr )P 0 .de ZP .nr pd \\n()P .nr )P 0 .if \\n(.$=0 .IP .if \\n(.$=1 .IP "\\$1" .if \\n(.$>=2 .IP "\\$1" "\\$2" .nr )P \\n(pd .rm pd .. .de LC .br .sp .1i .ne 4 .LP .ta 4.0i .. .\" .bd S B 3 .\".RP .TL Ex Reference Manual .br Version 3.7 .AU William Joy .AU Mark Horton .AI Computer Science Division Department of Electrical Engineering and Computer Science University of California, Berkeley Berkeley, Ca. 94720 .AB .I Ex is a line oriented text editor, which supports both command and display oriented editing. This reference manual describes the command oriented part of .I ex ; the display editing features of .I ex are described in .I "An Introduction to Display Editing with Vi" . Other documents about the editor include the introduction .I "Edit: A tutorial", the .I "Ex/edit Command Summary", and a .I "Vi Quick Reference" card. .AE .NH 1 Starting ex .PP .FS The financial support of an \s-2IBM\s0 Graduate Fellowship and the National Science Foundation under grants MCS74-07644-A03 and MCS78-07291 is gratefully acknowledged. .FE Each instance of the editor has a set of options, which can be set to tailor it to your liking. .\" The command .\" .I edit .\" invokes a version of .\" .I ex .\" designed for more casual or beginning .\" users by changing the default settings of some of these options. .\" To simplify the description which follows we .\" assume the default settings of the options. .PP When invoked, .I ex determines the terminal type from the \s-2TERM\s0 variable in the environment. .\" If there is a \s-2TERMCAP\s0 variable in the environment, and the type .\" of the terminal described there matches the \s-2TERM\s0 variable, .\" then that description .\" is used. Also if the \s-2TERMCAP\s0 variable contains a pathname (beginning .\" with a \fB/\fR) then the editor will seek the description of the terminal .\" in that file (rather than the default /etc/termcap). If there is a variable \s-2NEXINIT\s0 in the environment, then the editor will execute the commands in that variable, otherwise if there is a variable \s-2EXINIT\s0 in the environment, then the editor will execute the commands in that variable. If there is a file .I \&.nexrc in your \s-2HOME\s0 directory .I ex reads commands from that file, simulating a .I source command. Otherwise, if there is a file .I \&.exrc in your \s-2HOME\s0 directory, .I ex will read that. Additionally, .I ex will read startup commands from the current working directory, if they are placed in the files .I \&.nexrc or .I \&.exrc . Option setting commands placed in \s-2NEXINIT\s0, \s-2EXINIT\s0, .I \&.nexrc , or .I \&.exrc will be executed before each editor session. .PP A command to enter .I ex has the following prototype (brackets `[' `]' surround optional parameters here): .DS \fBex\fP [\fB\-\fP] [\fB\-FRrSsv\fP] [\fB\-c\fP \fIcmd\fP] [\fB\-t\fP \fItag\fP] [\fB\-w\fP \fIsize\fP] [\fB+\fP\fIcommand\fP] [\fIfile ...\fP] .DE The most common case edits a single file with no options, i.e.: .DS I ex name .DE The .B \- command line option option suppresses all interactive-user feedback and is useful in processing editor scripts in command files. The .B \-s option is the POSIX equivalent to .B \- ; both forms are identical. The .B \-v option is equivalent to using .I vi rather than .I ex . The .B \-t option is equivalent to an initial .I tag command, editing the file containing the .I tag and positioning the editor at its definition. The .B \-r option is used in recovering after an editor or system crash, retrieving the last saved version of the named file or, if no file is specified, typing a list of saved files. .\" The .\" .B \-l .\" option sets up for editing \s-2LISP\s0, setting the .\" .I showmatch .\" and .\" .I lisp .\" options. The .B \-w option sets the default window size to .I n , and is useful on dialups to start in small windows. .\" The .\" .B \-x .\" option causes .\" .I ex .\" to prompt for a .\" .I key , .\" which is used to encrypt and decrypt the contents of the file, .\" which should already be encrypted using the same key, .\" see .\" .I crypt (1). .PP The .B \-R option sets the .I readonly option at the start. The .B \-S option cause .I ex to be run with the .I secure option, disallowing all access to external programs. The .B \-F option prevents .I ex from copying the entire file at startup (the default is to make a copy of the file at startup). .PP .I Name arguments indicate files to be edited. An argument of the form \fB+\fIcommand\fR indicates that the editor should begin by executing the specified command. If .I command is omitted, then it defaults to ``$'', positioning the editor at the last line of the first file initially. Other useful commands here are scanning patterns of the form ``/pat'' or line numbers, e.g. ``+100'' starting at line 100. The form \fB\-c \fIcmd\fR is the POSIX equivalent to \fB+\fIcommand\fR; both forms are identical. .NH 1 File manipulation .NH 2 Current file .PP .I Ex is normally editing the contents of a single file, whose name is recorded in the .I current file name. .I Ex performs all editing actions in a buffer (actually a temporary file) into which the text of the file is initially read. Changes made to the buffer have no effect on the file being edited unless and until the buffer contents are written out to the file with a .I write command. After the buffer contents are written, the previous contents of the written file are no longer accessible. When a file is edited, its name becomes the current file name, and its contents are read into the buffer. .PP The current file is almost always considered to be .I edited . This means that the contents of the buffer are logically connected with the current file name, so that writing the current buffer contents onto that file, even if it exists, is a reasonable action. If the current file is not .I edited then .I ex will not normally write on it if it already exists.* .FS * The .I file command will say ``[Not edited]'' if the current file is not considered edited. .FE .NH 2 Alternate file .PP Each time a new value is given to the current file name, the previous current file name is saved as the .I alternate file name. Similarly if a file is mentioned but does not become the current file, it is saved as the alternate file name. .NH 2 Filename expansion .PP Filenames within the editor may be specified using the normal shell expansion conventions. In addition, the character `%' in filenames is replaced by the .I current file name and the character `#' by the .I alternate file name. This makes it easy to deal alternately with two files and eliminates the need for retyping the name supplied on an .I edit command after a .I "No write since last change" diagnostic is received. .NH 2 Multiple files and named buffers .PP If more than one file is given on the command line, then the first file is edited as described above. The remaining arguments are placed with the first file in the .I "argument list" . The current argument list may be displayed with the .I args command. The next file in the argument list may be edited with the .I next command. The argument list may also be respecified by specifying a list of names to the .I next command. These names are expanded, the resulting list of names becomes the new argument list, and .I ex edits the first file on the list. .PP For saving blocks of text while editing, and especially when editing more than one file, .I ex has a group of named buffers. These are similar to the normal buffer, except that only a limited number of operations are available on them. The buffers have names .I a through .I z . It is also possible to refer to .I A through .I Z ; the upper case buffers are the same as the lower but commands append to named buffers rather than replacing if upper case names are used. .NH 2 Read only .PP It is possible to use .I ex in .I "read only" mode to look at files that you have no intention of modifying. This mode protects you from accidentally overwriting the file. Read only mode is on when the .I readonly option is set. It can be turned on with the .B \-R command line option, by the .I view command line invocation, or by setting the .I readonly option. It can be cleared by setting .I noreadonly . It is possible to write, even while in read only mode, by indicating that you really know what you are doing. You can write to a different file, or can use the ! form of write, even while in read only mode. .NH 1 Exceptional Conditions .NH 2 Errors and interrupts .PP When errors occur .I ex (optionally) rings the terminal bell and, in any case, prints an error diagnostic. If the primary input is from a file, editor processing will terminate. If an interrupt signal is received, .I ex prints ``Interrupt'' and returns to its command level. If the primary input is a file, then .I ex will exit when this occurs. .NH 2 Recovering from hangups and crashes .PP If a hangup signal is received and the buffer has been modified since it was last written out, or if the system crashes, either the editor (in the first case) or the system (after it reboots in the second) will attempt to preserve the buffer. The next time you log in you should be able to recover the work you were doing, losing at most a few lines of changes from the last point before the hangup or editor crash. To recover a file you can use the .B \-r option. If you were editing the file .I resume , then you should change to the directory where you were when the crash occurred, giving the command .DS I ex -r resume .DE After checking that the retrieved file is indeed ok, you can .I write it over the previous contents of that file. .PP You will normally get mail from the system telling you when a file has been saved after a crash. The command .DS I ex -r .DE will print a list of the files which have been saved for you. (In the case of a hangup, the file will not appear in the list, although it can be recovered.) .NH 1 Editing modes .PP .I Ex has five distinct modes. The primary mode is .I command mode. Commands are entered in command mode when a `:' prompt is present, and are executed each time a complete line is sent. In .I "text input" mode, .I ex gathers input lines and places them in the file. The .I append , .I insert , and .I change commands use text input mode. No prompt is printed when you are in text input mode. This mode is left by typing a `.' alone at the beginning of a line, and .I command mode resumes. .PP The last two modes are .\" .I open .\" and .I visual mode, entered by the command of the same name, and, within visual mode .I "text insertion" mode. .\" .I Open .\" and .I Visual mode allows local editing operations to be performed on the text in the file. .\" The .\" .I open .\" command displays one line at a time on any terminal while .I Visual mode works on \s-2CRT\s0 terminals with random positioning cursors, using the screen as a window for file editing changes. These modes are described (only) in .I "An Introduction to Display Editing with Vi" . .NH Command structure .PP Most command names are English words, and initial prefixes of the words are acceptable abbreviations. The ambiguity of abbreviations is resolved in favor of the more commonly used commands.* .FS * As an example, the command .I substitute can be abbreviated `s' while the shortest available abbreviation for the .I set command is `se'. .FE .NH 2 Command parameters .PP Most commands accept prefix addresses specifying the lines in the file upon which they are to have effect. The forms of these addresses will be discussed below. A number of commands also may take a trailing .I count argument, specifying the number of lines to be involved in the command. Counts are rounded down if necessary. Thus the command ``10p'' will print the tenth line in the buffer while ``delete 5'' will delete five lines from the buffer, starting with the current line. .PP Some commands take other information or parameters, this information always being given after the command name. Examples would be option names in a .I set command i.e. ``set number'', a file name in an .I edit command, a regular expression in a .I substitute command, or a target address for a .I copy command, i.e. ``1,5 copy 25''. .NH 2 Command variants .PP A number of commands have two distinct variants. The variant form of the command is invoked by placing an `!' immediately after the command name. Some of the default variants may be controlled by options; in this case, the `!' serves to toggle the default. .NH 2 Flags after commands .PP The characters `#', `p' and `l' may be placed after many commands.** .FS ** A `p' or `l' must be preceded by a blank or tab except in the single special case `dp'. .FE In this case, the command abbreviated by these characters is executed after the command completes. Since .I ex normally prints the new current line after each change, `p' is rarely necessary. Any number of `+' or `\-' characters may also be given with these flags. If they appear, the specified offset is applied to the current line value before the printing command is executed. .NH 2 Comments .PP It is possible to give editor commands which are ignored. This is useful when making complex editor scripts for which comments are desired. The comment character is the double quote: ". Any command line beginning with " is ignored. Comments beginning with " may also be placed at the ends of commands, except in cases where they could be confused as part of text (shell escapes and the substitute and map commands). .NH 2 Multiple commands per line .PP More than one command may be placed on a line by separating each pair of commands by a `|' character. However the .I global commands, comments, and the shell escape `!' must be the last command on a line, as they are not terminated by a `|'. .NH 2 Reporting large changes .PP Most commands which change the contents of the editor buffer give feedback if the scope of the change exceeds a threshold given by the .I report option. This feedback helps to detect undesirably large changes so that they may be quickly and easily reversed with an .I undo . After commands with more global effect such as .I global or .I visual , you will be informed if the net change in the number of lines in the buffer during this command exceeds this threshold. .NH 1 Command addressing .NH 2 Addressing primitives .IP \fB.\fR 20 The current line. Most commands leave the current line as the last line which they affect. The default address for most commands is the current line, thus `\fB.\fR' is rarely used alone as an address. .IP \fIn\fR 20 The \fIn\fRth line in the editor's buffer, lines being numbered sequentially from 1. .IP \fB$\fR 20 The last line in the buffer. .IP \fB%\fR 20 An abbreviation for ``1,$''; the entire buffer. .IP \fI+n\fR,\ \fI\-n\fR 20 An offset relative to the current buffer line.* .FS * The forms `.+3', `+3', and `+++' are all equivalent; if the current line is line 100i, they all address line 103. .FE .IP \fB/\fIpat\fR\fB/\fR,\ \fB?\fIpat\fR\fB?\fR 20 Scan forward and backward respectively for a line containing \fIpat\fR, a regular expression (as defined below). The scans normally wrap around the end of the buffer. If all that is desired is to print the next line containing \fIpat\fR, then the trailing \fB/\fR or \fB?\fR may be omitted. If \fIpat\fP is omitted or explicitly empty, then the last regular expression specified is located.* .FS * The forms \fB\e/\fP and \fB\e?\fP scan using the last regular expression used in a scan; after a substitute \fB//\fP and \fB??\fP would scan using the substitute's regular expression. .FE .IP \fB\(aa\(aa\fP\ \fB\(aa\fP\fIx\fP 20 Before each non-relative motion of the current line `\fB.\fP', the previous current line is marked with a tag, subsequently referred to as \(aa\(aa. This makes it easy to refer or return to this previous context. Marks may also be established by the .I mark command, using single lower case letters .I x and the marked lines referred to as \(aa\fIx\fR. .NH 2 Combining addressing primitives .PP Addresses to commands consist of a series of addressing primitives, separated by `,' or `;'. Such address lists are evaluated left-to-right. When addresses are separated by `;' the current line `\fB.\fR' is set to the value of the previous addressing expression before the next address is interpreted. If more addresses are given than the command requires, then all but the last one or two are ignored. If the command takes two addresses, the first addressed line must precede the second in the buffer.** .FS ** NULL address specifications are permitted in a list of addresses, the default in this case is the current line `.'; thus `,100' is equivalent to `\fB.\fR,100'. It is an error to give a prefix address to a command which expects none. .FE .NH 1 Command descriptions .PP The following form is a prototype for all .I ex commands: .DS \fIaddress\fR \fBcommand\fR \fI! parameters count flags\fR .DE All parts are optional; the degenerate case is the empty command which prints the next line in the file. For sanity with use from within .I visual mode, .I ex ignores a ``:'' preceding any command. .PP In the following command descriptions, the default addresses are shown in parentheses, which are .I not , however, part of the command. .LC \fBabbreviate\fR \fIword rhs\fP abbr: \fBab\fP .ZP Add the named abbreviation to the current list. When in input mode in visual, if .I word is typed as a complete word, it will be changed to .I rhs . .LC ( \fB.\fR ) \fBappend\fR abbr: \fBa\fR .br \fItext\fR .br \&\fB.\fR .ZP Reads the input text and places it after the specified line. After the command, `\fB.\fR' addresses the last line input or the specified line if no lines were input. If address `0' is given, text is placed at the beginning of the buffer. .LC \fBa!\fR .br \fItext\fR .br \&\fB.\fR .ZP The variant flag to .I append toggles the setting for the .I autoindent option during the input of .I text . .LC \fBargs\fR .ZP The members of the argument list are printed, with the current argument delimited by `[' and `]'. .LC \fBcd\fR \fIdirectory\fR .ZP The .I cd command is a synonym for .I chdir . .LC ( \fB.\fP , \fB.\fP ) \fBchange\fP \fIcount\fP abbr: \fBc\fP .br \fItext\fP .br \&\fB.\fP .ZP Replaces the specified lines with the input \fItext\fP. The current line becomes the last line input; if no lines were input, it is left as for a \fIdelete\fP. .LC \fBc!\fP .br \fItext\fP .br \&\fB.\fP .ZP The variant toggles .I autoindent during the .I change. .LC \fBchdir\fR \fIdirectory\fR abbrev: \fBchd\fP .ZP The specified \fIdirectory\fR becomes the current directory. If no directory is specified, the current value of the .I home option is used as the target directory. After a .I chdir the current file is not considered to have been edited so that write restrictions on pre-existing files apply. .LC ( \fB.\fP , \fB.\fP )\|\fBcopy\fP \fIaddr\fP \fIflags\fP abbr: \fBco\fP .ZP A .I copy of the specified lines is placed after .I addr , which may be `0'. The current line `\fB.\fR' addresses the last line of the copy. The command .I t is a synonym for .I copy . .LC ( \fB.\fR , \fB.\fR )\|\fBdelete\fR \fIbuffer\fR \fIcount\fR \fIflags\fR abbr: \fBd\fR .ZP Removes the specified lines from the buffer. The line after the last line deleted becomes the current line; if the lines deleted were originally at the end, the new last line becomes the current line. If a named .I buffer is specified by giving a letter, then the specified lines are saved in that buffer, or appended to it if an upper case letter is used. .LC \fBedit\fR \fIfile\fR abbr: \fBe\fR .br \fBex\fR \fIfile\fR .ZP Used to begin an editing session on a new file. The editor first checks to see if the buffer has been modified since the last .I write command was issued. If it has been, a warning is issued and the command is aborted. The command otherwise deletes the entire contents of the editor buffer, makes the named file the current file and prints the new filename. After ensuring that this file is sensible* .FS * I.e., that it is not a special file such as a directory, a block or character special file other than .I /dev/tty , or a terminal. .FE the editor reads the file into its buffer. .IP If the read of the file completes without error, the number of lines and characters read is typed. If the last line of the input file is missing the trailing newline character, it will be supplied and a complaint will be issued. This command leaves the current line `\fB.\fR' at the last line read.** .FS ** If executed from within .\" .I open .\" or .I visual, the current line is initially the first line of the file. .FE .LC \fBe!\fR \fIfile\fR .ZP The variant form suppresses the complaint about modifications having been made and not written from the editor buffer, thus discarding all changes which have been made before editing the new file. .LC \fBe\fR \fB+\fIn\fR \fIfile\fR .ZP Causes the editor to begin at line .I n rather than at the last line; \fIn\fR may also be an editor command containing no spaces, e.g.: ``+/pat''. .LC \fBfile\fR abbr: \fBf\fR .ZP Prints the current file name, whether it has been `[Modified]' since the last .I write command, whether it is .I "read only" , the current line, the number of lines in the buffer, and the percentage of the way through the buffer of the current line.* .FS * In the rare case that the current file is `[Not edited]' this is noted also; in this case you have to use the form \fBw!\fR to write to the file, since the editor is not sure that a \fBwrite\fR will not destroy a file unrelated to the current contents of the buffer. .FE .LC \fBfile\fR \fIfile\fR .ZP The current file name is changed to .I file , which is considered `[Not edited]'. .LC ( 1 , $ ) \fBglobal\fR /\fIpat\|\fR/ \fIcmds\fR abbr: \fBg\fR .ZP First marks each line among those specified which matches the given regular expression. Then the given command list is executed with `\fB.\fR' initially set to each marked line. .IP The command list consists of the remaining commands on the current input line and may continue to multiple lines by ending all but the last such line with a `\e'. If .I cmds (and possibly the trailing \fB/\fR delimiter) is omitted, each line matching .I pat is printed. .I Append , .I insert , and .I change commands and associated input are permitted; the `\fB.\fR' terminating input may be omitted if it would be on the last line of the command list. .\" .I Open .\" and .I Visual commands are permitted in the command list and take input from the terminal. .IP The .I global command itself may not appear in .I cmds . The .I undo command is also not permitted there, as .I undo instead can be used to reverse the entire .I global command. The options .I autoprint and .I autoindent are inhibited during a .I global , (and possibly the trailing \fB/\fR delimiter) and the value of the .I report option is temporarily infinite, in deference to a \fIreport\fR for the entire global. Finally, the context mark ('') is set to the value of `.' before the global command begins and is not changed during a global command, except perhaps by a .\" .I open .\" or .I visual within the .I global . .LC \fBg!\fR \fB/\fIpat\fB/\fR \fIcmds\fR abbr: \fBv\fR .IP The variant form of \fIglobal\fR runs \fIcmds\fR at each line not matching \fIpat\fR. .LC ( \fB.\fR )\|\fBinsert\fR abbr: \fBi\fR .br \fItext\fR .br \&\fB.\fR .ZP Places the given text before the specified line. The current line is left at the last line input; if there were none input it is left at the line before the addressed line. This command differs from .I append only in the placement of text. .KS .LC \fBi!\fR .br \fItext\fR .br \&\fB.\fR .ZP The variant toggles .I autoindent during the .I insert. .KE .LC ( \fB.\fR , \fB.\fR+1 ) \fBjoin\fR \fIcount\fR \fIflags\fR abbr: \fBj\fR .ZP Places the text from a specified range of lines together on one line. Whitespace is adjusted at each junction to provide at least one blank character, two if there was a `\fB.\fR' at the end of the line, or none if the first following character is a `)'. If there is already whitespace at the end of the line, then the whitespace at the start of the next line will be discarded. .LC \fBj!\fR .ZP The variant causes a simpler .I join with no whitespace processing; the characters in the lines are simply concatenated. .LC ( \fB.\fR ) \fBk\fR \fIx\fR .ZP The .I k command is a synonym for .I mark . It does not require a blank or tab before the following letter. .LC ( \fB.\fR , \fB.\fR ) \fBlist\fR \fIcount\fR \fIflags\fR .ZP Prints the specified lines in a more unambiguous way: tabs are printed as `^I' and the end of each line is marked with a trailing `$'. The current line is left at the last line printed. .LC \fBmap\fR \fIlhs\fR \fIrhs\fR .ZP The .I map command is used to define macros for use in .I visual mode. .I Lhs should be a single character, or the sequence ``#n'', for n a digit, referring to function key \fIn\fR. When this character or function key is typed in .I visual mode, it will be as though the corresponding \fIrhs\fR had been typed. On terminals without function keys, you can type ``#n''. See section 6.8 of the ``Introduction to Display Editing with Vi'' for more details. .LC ( \fB.\fR ) \fBmark\fR \fIx\fR .ZP Gives the specified line mark .I x , a single lower case letter. The .I x must be preceded by a blank or a tab. The addressing form 'x then addresses this line. The current line is not affected by this command. .LC ( \fB.\fR , \fB.\fR ) \fBmove\fR \fIaddr\fR abbr: \fBm\fR .ZP The .I move command repositions the specified lines to be after .I addr . The first of the moved lines becomes the current line. .LC \fBnext\fR abbr: \fBn\fR .ZP The next file from the command line argument list is edited. .LC \fBn!\fR .ZP The variant suppresses warnings about the modifications to the buffer not having been written out, discarding (irretrievably) any changes which may have been made. .LC \fBn\fR \fIfilelist\fR .br \fBn\fR \fB+\fIcommand\fR \fIfilelist\fR .ZP The specified .I filelist is expanded and the resulting list replaces the current argument list; the first file in the new list is then edited. If .I command is given (it must contain no spaces), then it is executed after editing the first such file. .LC ( \fB.\fR , \fB.\fR ) \fBnumber\fR \fIcount\fR \fIflags\fR abbr: \fB#\fR or \fBnu\fR .ZP Prints each specified line preceded by its buffer line number. The current line is left at the last line printed. .\" .KS .\" .LC .\" ( \fB.\fR ) \fBopen\fR \fIflags\fR abbr: \fBo\fR .\" .br .\" ( \fB.\fR ) \fBopen\fR /\fIpat\|\fR/ \fIflags\fR .\" .ZP .\" Enters intraline editing \fIopen\fR mode at each addressed line. .\" If .\" .I pat .\" is given, .\" then the cursor will be placed initially at the beginning of the .\" string matched by the pattern. .\" To exit this mode use Q. .\" See .\" .I "An Introduction to Display Editing with Vi" .\" for more details. .\" .KE .LC \fBpreserve\fR abbrev: \fBpre\fR .ZP The current editor buffer is saved as though the system had just crashed. This command is for use only in emergencies when a .I write command has resulted in an error and you don't know how to save your work. After a .I preserve you should seek help. .LC ( \fB.\fR , \fB.\fR )\|\fBprint\fR \fIcount\fR abbr: \fBp\fR or \fBP\fR .ZP Prints the specified lines with non-printing characters printed as control characters `^\fIx\fR\|'; delete (octal 177) is represented as `^?'. The current line is left at the last line printed. .LC ( \fB.\fR )\|\fBput\fR \fIbuffer\fR abbr: \fBpu\fR .ZP Puts back previously .I deleted or .I yanked lines. Normally used with .I delete to effect movement of lines, or with .I yank to effect duplication of lines. If no .I buffer is specified, then the last .I deleted or .I yanked text is restored.* .FS * But no modifying commands may intervene between the .I delete or .I yank and the .I put , nor may lines be moved between files without using a named buffer. .FE By using a named buffer, text may be restored that was saved there at any previous time. .LC \fBquit\fR abbr: \fBq\fR .ZP Causes .I ex to terminate. No automatic write of the editor buffer to a file is performed. However, .I ex issues a warning message if the file has changed since the last .I write command was issued, and does not .I quit .** .FS ** \fIEx\fR will also issue a diagnostic if there are more files in the argument list. .FE Normally, you will wish to save your changes, and you should give a \fIwrite\fR command; if you wish to discard them, use the \fBq!\fR command variant. .LC \fBq!\fR .ZP Quits from the editor, discarding changes to the buffer without complaint. .LC ( \fB.\fR ) \fBread\fR \fIfile\fR abbr: \fBr\fR .ZP Places a copy of the text of the given file in the editing buffer after the specified line. If no .I file is given, the current file name is used. The current file name is not changed unless there is none, in which case .I file becomes the current name. The sensibility restrictions for the .I edit command apply here also. If the file buffer is empty and there is no current name then .I ex treats this as an .I edit command. .IP Address `0' is legal for this command and causes the file to be read at the beginning of the buffer. Statistics are given as for the .I edit command when the .I read successfully terminates. After a .I read the current line is the last line read.* .FS * Within .\" .I open .\" and .I visual mode, the current line is set to the first line read rather than the last. .FE .LC ( \fB.\fR ) \fBread\fR \fB!\fR\fIcommand\fR .ZP Reads the output of the command .I command into the buffer after the specified line. This is not a variant form of the command, rather a read specifying a .I command rather than a .I filename ; a blank or tab before the \fB!\fR is mandatory. .LC \fBrecover \fIfile\fR .ZP Recovers .I file from the system save area. Used, for example, after a remote connection has timed out** .FS ** The system saves a copy of the file you were editing only if you have made changes to the file. .FE or a system crash** or .I preserve command. Except when you use .I preserve you will be notified by mail when a file is saved. .LC \fBrewind\fR abbr: \fBrew\fR .ZP The argument list is rewound, and the first file in the list is edited. .LC \fBrew!\fR .ZP Rewinds the argument list discarding any changes made to the current buffer. .LC \fBset\fR \fIparameter\fR .ZP With no arguments, prints those options whose values have been changed from their defaults; with parameter .I all it prints all of the option values. .IP Giving an option name followed by a `?' causes the current value of that option to be printed. The `?' is unnecessary unless the option is Boolean valued. Boolean options are given values either by the form `set \fIoption\fR' to turn them on or `set no\fIoption\fR' to turn them off; string and numeric options are assigned via the form `set \fIoption\fR=value'. .IP More than one parameter may be given to .I set ; they are interpreted left-to-right. .LC \fBshell\fR abbr: \fBsh\fR .IP A new shell is created. When it terminates, editing resumes. .LC \fBsource\fR \fIfile\fR abbr: \fBso\fR .IP Reads and executes commands from the specified file. .I Source commands may be nested. .LC ( \fB.\fR , \fB.\fR ) \fBsubstitute\fR /\fIpat\fR\|/\fIrepl\fR\|/ \fIoptions\fR \fIcount\fR \fIflags\fR\ \&abbr: \fBs\fR .IP On each specified line, the first instance of pattern .I pat is replaced by replacement pattern .I repl . If the .I global indicator option character `g' appears, then all instances are substituted; if the .I confirm indication character `c' appears, then before each substitution the line to be substituted is typed with the string to be substituted marked with `^' characters. By typing a `y' one can cause the substitution to be performed, any other input causes no change to take place. After a .I substitute the current line is the last line substituted. .IP Lines may be split by substituting new-line characters into them. The newline in .I repl must be escaped by preceding it with a `\e'. Other metacharacters available in .I pat and .I repl are described below. .LC .B stop .ZP Suspends the editor, returning control to the top level shell. If .I autowrite is set and there are unsaved changes, a write is done first unless the form .B stop ! is used. This command is only available where supported by the tty driver and operating system. .LC ( \fB.\fR , \fB.\fR ) \fBsubstitute\fR \fIoptions\fR \fIcount\fR \fIflags\fR abbr: \fBs\fR .ZP If .I pat and .I repl are omitted, then the last substitution is repeated. This is a synonym for the .B & command. .LC ( \fB.\fR , \fB.\fR ) \fBt\fR \fIaddr\fR \fIflags\fR .ZP The .I t command is a synonym for .I copy . .LC \fBtag\fR \fItagstring\fR abbrev: ta .ZP The focus of editing switches to the location of .I tagstring , switching to a different line in the current file where it is defined, or if necessary to another file.* .FS * If you have modified the current file before giving a .I tag command, you must write it out; giving another .I tag command, specifying no .I tag will reuse the previous tag. .FE .IP The tags file is normally created by a program such as .I ctags , and consists of a number of lines with three fields separated by blanks or tabs. The first field gives the name of the tag, the second the name of the file where the tag resides, and the third gives an addressing form which can be used by the editor to find the tag; this field is usually a contextual scan using `/\fIpat\fR/' to be immune to minor changes in the file. Such scans are always performed as if .I nomagic was set. .IP The tag names in the tags file must be sorted alphabetically. .LC \fBunabbreviate\fR \fIword\fP abbr: \fBuna\fP .ZP Delete .I word from the list of abbreviations. .LC \fBundo\fR abbr: \fBu\fR .ZP Reverses the changes made in the buffer by the last buffer editing command. Note that .I global commands are considered a single command for the purpose of .I undo (as is .\" .I open .\" and .I visual .) Also, the commands .I write and .I edit which interact with the file system cannot be undone. .I Undo is its own inverse. .IP .I Undo always marks the previous value of the current line `\fB.\fR' as ''. After an .I undo the current line is the first line restored or the line before the first line deleted if no lines were restored. For commands with more global effect such as .I global and .I visual the current line regains it's pre-command value after an .I undo . .LC \fBunmap\fR \fIlhs\fR .ZP The macro expansion associated by .I map for .I lhs is removed. .LC ( 1 , $ ) \fBv\fR /\fIpat\fR\|/ \fIcmds\fR .ZP A synonym for the .I global command variant \fBg!\fR, running the specified \fIcmds\fR on each line which does not match \fIpat\fR. .LC \fBversion\fR abbr: \fBve\fR .ZP Prints the current version number of the editor as well as the date the editor was last changed. .LC ( \fB.\fR ) \fBvisual\fR \fItype\fR \fIcount\fR \fIflags\fR abbr: \fBvi\fR .ZP Enters visual mode at the specified line. .I Type is optional and may be `\-' , `^' or `\fB.\fR' as in the .I z command to specify the placement of the specified line on the screen. By default, if .I type is omitted, the specified line is placed as the first on the screen. A .I count specifies an initial window size; the default is the value of the option .I window . See the document .I "An Introduction to Display Editing with Vi" for more details. To exit this mode, type .I Q . .LC \fBvisual\fP file .br \fBvisual\fP +\fIn\fP file .ZP From visual mode, this command is the same as edit. .LC ( 1 , $ ) \fBwrite\fR \fIfile\fR abbr: \fBw\fR .ZP Writes changes made back to \fIfile\fR, printing the number of lines and characters written. Normally \fIfile\fR is omitted and the text goes back where it came from. If a \fIfile\fR is specified, then text will be written to that file.* .FS * The editor writes to a file only if it is the current file and is .I edited , if the file does not exist, or if the file is actually a teletype, .I /dev/tty , .I /dev/null . Otherwise, you must give the variant form \fBw!\fR to force the write. .FE If the file does not exist, it is created. The current file name is changed only if there is no current file name; the current line is never changed. .IP If an error occurs while writing the current and .I edited file, the editor considers that there has been ``No write since last change'' even if the buffer had not previously been modified. .LC ( 1 , $ ) \fBwrite>>\fR \fIfile\fR abbr: \fBw>>\fR .ZP Writes the buffer contents at the end of an existing file. .IP .LC \fBw!\fR \fIname\fR .ZP Overrides the checking of the normal \fIwrite\fR command, and will write to any file which the system permits. .LC ( 1 , $ ) \fBw\fR \fB!\fR\fIcommand\fR .ZP Writes the specified lines into .I command. Note the difference between \fBw!\fR which overrides checks and \fBw\ \ !\fR which writes to a command. .LC \fBwq\fR \fIname\fR .ZP Like a \fIwrite\fR and then a \fIquit\fR command. .LC \fBwq!\fR \fIname\fR .ZP The variant overrides checking on the sensibility of the .I write command, as \fBw!\fR does. .LC \fBxit\fP \fIname\fR abbr: \fBx\fR .ZP If any changes have been made and not written, writes the buffer out. Then, in any case, quits. .LC ( \fB.\fR , \fB.\fR )\|\fByank\fR \fIbuffer\fR \fIcount\fR abbr: \fBya\fR .ZP Places the specified lines in the named .I buffer, for later retrieval via .I put. If no buffer name is specified, the lines go to a more volatile place; see the \fIput\fR command description. .LC ( \fB.+1\fR ) \fBz\fR \fIcount\fR .ZP Print the next \fIcount\fR lines, default \fIwindow\fR. .LC ( \fB.\fR ) \fBz\fR \fItype\fR \fIcount\fR .ZP Prints a window of text with the specified line at the top. If \fItype\fR is `\-', the line is placed at the bottom; a `\fB.\fR' causes the line to be placed in the center.* A count gives the number of lines to be displayed rather than double the number specified by the \fIscroll\fR option. On a \s-2CRT\s0 the screen is cleared before display begins unless a count which is less than the screen size is given. The current line is left at the last line printed. .FS * Forms `z=' and `z^' also exist; `z=' places the current line in the center, surrounds it with lines of `\-' characters and leaves the current line at this line. The form `z^' prints the window before `z\-' would. The characters `+', `^' and `\-' may be repeated for cumulative effect. On some v2 editors, no .I type may be given. .FE .LC \fB!\fR \fIcommand\fR\fR .ZP The remainder of the line after the `!' character is sent to a shell to be executed. Within the text of .I command the characters `%' and `#' are expanded as in filenames and the character `!' is replaced with the text of the previous command. Thus, in particular, `!!' repeats the last such shell escape. If any such expansion is performed, the expanded line will be echoed. The current line is unchanged by this command. .IP If there has been ``[No\ write]'' of the buffer contents since the last change to the editing buffer, then a diagnostic will be printed before the command is executed as a warning. A single `!' is printed when the command completes. .LC ( \fIaddr\fR , \fIaddr\fR ) \fB!\fR \fIcommand\fR\fR .ZP Takes the specified address range and supplies it as standard input to .I command ; the resulting output then replaces the input lines. .LC ( $ ) \fB=\fR .ZP Prints the line number of the addressed line. The current line is unchanged. .KS .LC ( \fB.\fR , \fB.\fR ) \fB>\fR \fIcount\fR \fIflags\fR .br ( \fB.\fR , \fB.\fR ) \fB<\fR \fIcount\fR \fIflags\fR .IP Perform intelligent shifting on the specified lines; \fB<\fR shifts left and \fB>\fR shifts right. The quantity of shift is determined by the .I shiftwidth option and the repetition of the specification character. Only whitespace (blanks and tabs) is shifted; no non-white characters are discarded in a left-shift. The current line becomes the last line which changed due to the shifting. .KE .LC \fB^D\fR .ZP An end-of-file from a terminal input scrolls through the file. The .I scroll option specifies the size of the scroll, normally a half screen of text. .LC ( \fB.\fR+1 , \fB.\fR+1 ) .br ( \fB.\fR+1 , \fB.\fR+1 ) .ZP An address alone causes the addressed lines to be printed. A blank line prints the next line in the file. .LC ( \fB.\fR , \fB.\fR ) \fB&\fR \fIoptions\fR \fIcount\fR \fIflags\fR .ZP Repeats the previous .I substitute command. .LC ( \fB.\fR , \fB.\fR ) \fB\s+2~\s0\fR \fIoptions\fR \fIcount\fR \fIflags\fR .ZP Replaces the previous regular expression with the previous replacement pattern from a substitution. .NH 1 Regular expressions and substitute replacement patterns .NH 2 Regular expressions .PP A regular expression specifies a set of strings of characters. A member of this set of strings is said to be .I matched by the regular expression. .I Ex remembers two previous regular expressions: the previous regular expression used in a .I substitute command and the previous regular expression used elsewhere (referred to as the previous \fIscanning\fR regular expression.) The previous regular expression can always be referred to by a NULL \fIre\fR, e.g. `//' or `??'. .NH 2 Magic and nomagic .PP The regular expressions allowed by .I ex are constructed in one of two ways depending on the setting of the .I magic option. The .I ex and .I vi default setting of .I magic gives quick access to a powerful set of regular expression metacharacters. The disadvantage of .I magic is that the user must remember that these metacharacters are .I magic and precede them with the character `\e' to use them as ``ordinary'' characters. With .I nomagic , .\" the default for .\" .I edit , regular expressions are much simpler, there being only two metacharacters. The power of the other metacharacters is still available by preceding the (now) ordinary character with a `\e'. Note that `\e' is thus always a metacharacter. .PP The remainder of the discussion of regular expressions assumes that that the setting of this option is .I magic .* .FS * To discern what is true with .I nomagic it suffices to remember that the only special characters in this case will be `^' at the beginning of a regular expression, `$' at the end of a regular expression, and `\e'. With .I nomagic the characters `\s+2~\s0' and `&' also lose their special meanings related to the replacement pattern of a substitute. .FE .NH 2 Basic regular expression summary .PP The following basic constructs are used to construct .I magic mode regular expressions. .IP \fIchar\fR 15 An ordinary character matches itself. The characters `^' at the beginning of a line, `$' at the end of line, `*' as any character other than the first, `.', `\e', `[', and `\s+2~\s0' are not ordinary characters and must be escaped (preceded) by `\e' to be treated as such. .IP \fB^\fR At the beginning of a pattern forces the match to succeed only at the beginning of a line. .IP \fB$\fR At the end of a regular expression forces the match to succeed only at the end of the line. .IP \&\fB.\fR Matches any single character except the new-line character. .IP \fB\e<\fR Forces the match to occur only at the beginning of a ``variable'' or ``word''; that is, either at the beginning of a line, or just before a letter, digit, or underline and after a character not one of these. .IP \fB\e>\fR Similar to `\e<', but matching the end of a ``variable'' or ``word'', i.e. either the end of the line or before character which is neither a letter, nor a digit, nor the underline character. .IP \fB[\fIstring\fB]\fR Matches any (single) character in the class defined by .I string . Most characters in .I string define themselves. A pair of characters separated by `\-' in .I string defines the set of characters collating between the specified lower and upper bounds, thus `[a\-z]' as a regular expression matches any (single) lower-case letter. If the first character of .I string is an `^' then the construct matches those characters which it otherwise would not; thus `[^a\-z]' matches anything but a lower-case letter (and of course a newline). To place any of the characters `^', `[', or `\-' in .I string you must escape them with a preceding `\e'. .\" .\" document extended regexps (set extended) .\" .NH 2 Combining regular expression primitives .PP The concatenation of two regular expressions matches the leftmost and then longest string which can be divided with the first piece matching the first regular expression and the second piece matching the second. Any of the (single character matching) regular expressions mentioned above may be followed by the character `*' to form a regular expression which matches any number of adjacent occurrences (including 0) of characters matched by the regular expression it follows. .PP The character `\s+2~\s0' may be used in a regular expression, and matches the text which defined the replacement part of the last .I substitute command. A regular expression may be enclosed between the sequences `\e(' and `\e)' with side effects in the .I substitute replacement patterns. .NH 2 Substitute replacement patterns .PP The basic metacharacters for the replacement pattern are `&' and `~'; these are given as `\e&' and `\e~' when .I nomagic is set. Each instance of `&' is replaced by the characters which the regular expression matched. The metacharacter `~' stands, in the replacement pattern, for the defining text of the previous replacement pattern. .PP Other metasequences possible in the replacement pattern are always introduced by the escaping character `\e'. The sequence `\e\fIn\fR' is replaced by the text matched by the \fIn\fR-th regular subexpression enclosed between `\e(' and `\e)'.* .FS * When nested, parenthesized subexpressions are present, \fIn\fR is determined by counting occurrences of `\e(' starting from the left. .FE The sequences `\eu' and `\el' cause the immediately following character in the replacement to be converted to upper- or lower-case respectively if this character is a letter. The sequences `\eU' and `\eL' turn such conversion on, either until `\eE' or `\ee' is encountered, or until the end of the replacement pattern. .de LC .br .sp .1i .ne 4 .LP .ta 3i .. .NH 1 Option descriptions .PP .LC \fBautoindent\fR, \fBai\fR default: noai .ZP Can be used to ease the preparation of structured program text. At the beginning of each .I append , .I change , or .I insert command or when a new line is opened or created by an .I append , .I change , .I insert , or .I substitute operation within .\" .I open .\" or .I visual mode, .I ex looks at the line being appended after, the first line changed or the line inserted before and calculates the amount of whitespace at the start of the line. It then aligns the cursor at the level of indentation so determined. .IP If the user then types lines of text in, they will continue to be justified at the displayed indenting level. If more whitespace is typed at the beginning of a line, the following line will start aligned with the first non-white character of the previous line. To back the cursor up to the preceding tab stop one can hit \fB^D\fR. The tab stops going backwards are defined at multiples of the .I shiftwidth option. You .I cannot backspace over the indent, except by sending an end-of-file with a \fB^D\fR. .IP Specially processed in this mode is a line with no characters added to it, which turns into a completely blank line (the whitespace provided for the .I autoindent is discarded.) Also specially processed in this mode are lines beginning with a `^' and immediately followed by a \fB^D\fR. This causes the input to be repositioned at the beginning of the line, but retaining the previous indent for the next line. Similarly, a `0' followed by a \fB^D\fR repositions at the beginning but without retaining the previous indent. .IP .I Autoindent doesn't happen in .I global commands or when the input is not a terminal. .LC \fBautoprint\fR, \fBap\fR default: ap .ZP Causes the current line to be printed after each .I delete , .I copy , .I join , .I move , .I substitute , .I t , .I undo , or .I shift command. This has the same effect as supplying a trailing `p' to each such command. .I Autoprint is suppressed in globals, and only applies to the last of many commands on a line. .LC \fBautowrite\fR, \fBaw\fR default: noaw .ZP Causes the contents of the buffer to be written to the current file if you have modified it and give a .I next , .I rewind , .I stop , .I tag , or .I ! command, or a \fB^^\fR (switch files) or \fB^]\fR (tag goto) command in .I visual . Note, that the .\" .I edit .\" and .I ex command does .B not autowrite. In each case, there is an equivalent way of switching when autowrite is set to avoid the .I autowrite (\fIedit\fR for .I next , .I rewind! for .I rewind , .I stop! for .I stop , .I tag! for .I tag , .I shell for .I ! , and \fB:e\ #\fR and a \fB:ta!\fR command from within .I visual ). .LC \fBbeautify\fR, \fBbf\fR default: nobeautify .ZP Causes all control characters except tab, newline and form-feed to be discarded from the input. A complaint is registered the first time a backspace character is discarded. .I Beautify does not apply to command input. .LC \fBdirectory\fR, \fBdir\fR default: dir=/tmp .ZP Specifies the directory in which .I ex places its buffer file. If this directory in not writable, then the editor will exit abruptly when it fails to create its buffer there. .LC \fBedcompatible\fR default: noedcompatible .ZP Causes the presence or absence of .B g and .B c suffixes on substitute commands to be remembered, and to be toggled by repeating the suffices. The suffix .B r makes the substitution be as in the .I ~ command, instead of like .I & . .LC \fBerrorbells\fR, \fBeb\fR default: noeb .ZP Error messages are preceded by a bell.* .FS * Bell ringing in .\" .I open .\" and .I visual mode on errors is not suppressed by setting .I noeb . .FE If possible the editor always places the error message in a standout mode of the terminal (such as inverse video) instead of ringing the bell. .LC \fBhardtabs\fR, \fBht\fR default: ht=0 .ZP Gives the boundaries on which terminal hardware tabs are set (or on which the system expands tabs). .LC \fBignorecase\fR, \fBic\fR default: noic .ZP All upper case characters in the text are mapped to lower case in regular expression matching. In addition, all upper case characters in regular expressions are mapped to lower case except in character class specifications. .\" .LC .\" \fBlisp\fR default: nolisp .\" .ZP .\" \fIAutoindent\fR indents appropriately for .\" .I lisp .\" code, and the \fB( ) { } [[\fR and \fB]]\fR commands in .\" .I open .\" and .\" .I visual .\" are modified to have meaning for \fIlisp\fR. .LC \fBlist\fR default: nolist .ZP All printed lines will be displayed (more) unambiguously, showing tabs and end-of-lines as in the .I list command. .LC \fBmagic\fR default: magic for \fIex\fR and \fIvi\fR .\" .FS .\" \(dg \fINomagic\fR for \fIedit\fR. .\" .FE .ZP If .I nomagic is set, the number of regular expression metacharacters is greatly reduced, with only `^' and `$' having special effects. In addition the metacharacters `~' and `&' of the replacement pattern are treated as normal characters. All the normal metacharacters may be made .I magic when .I nomagic is set by preceding them with a `\e'. .LC \fBmesg\fR default: mesg .ZP Causes write permission to be turned off to the terminal while you are in visual mode, if .I nomesg is set. .LC \fBmodeline\fR default: nomodeline .ZP If .I modeline is set, then the first 5 lines and the last five lines of the file will be checked for ex command lines and the commands issued. To be recognized as a command line, the line must have the string .B ex: or .B vi: preceded by a tab or a space. This string may be anywhere in the line and anything after the .B : is interpreted as editor commands. This option defaults to off because of unexpected behavior when editing files such as .I /etc/passwd . .LC \fBnumber, nu\fR default: nonumber .ZP Causes all output lines to be printed with their line numbers. In addition each input line will be prompted for by supplying the line number it will have. .LC \fBopen\fR default: open .ZP If \fInoopen\fR, the commands .I open and .I visual are not permitted. .\" This is set for .\" .I edit .\" to prevent confusion resulting from accidental entry to .\" open or visual mode. .LC \fBoptimize, opt\fR default: optimize .ZP Throughput of text is expedited by setting the terminal to not do automatic carriage returns when printing more than one (logical) line of output, greatly speeding output on terminals without addressable cursors when text with leading whitespace is printed. .LC \fBparagraphs,\ para\fR default: para=IPLPPPQPP\ \&LIpplpipbp .ZP Specifies the paragraphs for the \fB{\fR and \fB}\fR operations in .\" .I open .\" and .I visual mode. The pairs of characters in the option's value are the names of the macros which start paragraphs. .LC \fBprompt\fR default: prompt .ZP Command mode input is prompted for with a `:'. .LC \fBredraw\fR default: noredraw .ZP The editor simulates (using great amounts of output), an intelligent terminal on a dumb terminal (e.g. during insertions in .I visual the characters to the right of the cursor position are refreshed as each input character is typed). Useful only at very high speed. .LC \fBremap\fP default: remap .ZP If on, macros are repeatedly tried until they are unchanged. For example, if .B o is mapped to .B O , and .B O is mapped to .B I , then if .I remap is set, .B o will map to .B I , but if .I noremap is set, it will map to .B O . .LC \fBreport\fR default: report=5 .\" .FS .\" \(dg 2 for \fIedit\fR. .\" .FE .ZP Specifies a threshold for feedback from commands. Any command which modifies more than the specified number of lines will provide feedback as to the scope of its changes. For commands such as .I global , .I open , .I undo , and .I visual which have potentially more far reaching scope, the net change in the number of lines in the buffer is presented at the end of the command, subject to this same threshold. Thus notification is suppressed during a .I global command on the individual commands performed. .LC \fBscroll\fR default: scroll=\(12 window .ZP Determines the number of logical lines scrolled when an end-of-file is received from a terminal input in command mode, and the number of lines printed by a command mode .I z command (double the value of .I scroll ). .LC \fBsections\fR default: sections=NHSHH\ \&HU .ZP Specifies the section macros for the \fB[[\fR and \fB]]\fR operations in .\" .I open .\" and .I visual mode. The pairs of characters in the options's value are the names of the macros which start paragraphs. .LC \fBshell\fR, \fBsh\fR default: sh=/bin/sh .ZP Gives the path name of the shell forked for the shell escape command `!', and by the .I shell command. The default is taken from SHELL in the environment, if present. .LC \fBshiftwidth\fR, \fBsw\fR default: sw=8 .ZP Gives the width a software tab stop, used in reverse tabbing with \fB^D\fR when using .I autoindent to append text, and by the shift commands. .LC \fBshowmatch, sm\fR default: nosm .ZP In .I open and .I visual mode, when a \fB)\fR or \fB}\fR is typed, move the cursor to the matching \fB(\fR or \fB{\fR for one second if this matching character is on the screen. .\" Extremely useful with .\" .I lisp. .\" .LC .\" \fBslowopen, slow\fR terminal dependent .\" .ZP .\" Affects the display algorithm used in .\" .I visual .\" mode, holding off display updating during input of new text to improve .\" throughput when the terminal in use is both slow and unintelligent. .\" See .\" .I "An Introduction to Display Editing with Vi" .\" for more details. .LC \fBtabstop,\ ts\fR default: ts=8 .ZP The editor expands tabs in the input file to be on .I tabstop boundaries for the purposes of display. .LC \fBtaglength,\ tl\fR default: tl=0 .ZP Tags are not significant beyond this many characters. A value of zero (the default) means that all characters are significant. .LC \fBtags\fR default: tags=tags .ZP A path of files to be used as tag files for the .I tag command. A requested tag is searched for in the specified files, sequentially. By default, files called .B tags are searched for in the current directory. .\" and in /usr/lib .\" (a master file for the entire system). .LC \fBterm\fR from environment TERM .ZP The terminal type of the output device. .LC \fBterse\fR default: noterse .ZP Shorter error diagnostics are produced for the experienced user. .LC \fBwarn\fR default: warn .ZP Warn if there has been `[No write since last change]' before a `!' command escape. .LC \fBwindow\fR from environment LINES .ZP The number of lines in a text window in the .I visual command. The default is 8 at slow speeds (600 baud or less), 16 at medium speed (1200 baud), and the full screen (minus one line) at higher speeds. .LC \fBw300,\ w1200\, w9600\fR .ZP These are not true options but set .B window only if the speed is slow (300), medium (1200), or high (9600), respectively. They are suitable for an EXINIT and make it easy to change the 8/16/full screen rule. .LC \fBwrapscan\fR, \fBws\fR default: ws .ZP Searches using the regular expressions in addressing will wrap around past the end of the file. .LC \fBwrapmargin\fR, \fBwm\fR default: wm=0 .ZP Defines a margin for automatic wrapover of text during input in .\" .I open .\" and .I visual mode. See .I "An Introduction to Text Editing with Vi" for details. .LC \fBwriteany\fR, \fBwa\fR default: nowa .IP Inhibit the checks normally made before .I write commands, allowing a write to any file which the system protection mechanism will allow. .NH 1 Acknowledgements .PP Chuck Haley contributed greatly to the early development of .I ex . Bruce Englar encouraged the redesign which led to .I ex version 1. Bill Joy wrote versions 1 and 2.0 through 2.7, and created the framework that users see in the present editor. Mark Horton added macros and other features and made the editor work on a large number of terminals and Unix systems. ================================================ FILE: docs/USD.doc/exref/ex.summary ================================================ .\" $OpenBSD: ex.summary,v 1.6 2004/01/30 23:14:26 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)ex.summary 8.3 (Berkeley) 8/18/96 .\" .ds p \v'-0.2'.\v'+0.2' .ds U \s-2UNIX\s+2 .ds c \v'-0.2':\v'+0.2' .nr LL 6.5i .lt 6.5i .ll 6.5i .ds CH .ds LF Computing Services, U.C. Berkeley .ds RF April 3, 1979 .de SP .sp 1v .. .nr PI 3n .nr PD 0 .ND .ps 12 .ft B .ce 1 .\" Ex/Edit Command Summary (Version 2.0) Ex Command Summary (Version 2.0) .sp 1 .ft R .nr VS 11 .nr PS 9 .\" .2C .PP .I Ex .\" and .\" .I edit is a text editor, used for creating and modifying files of text on the \*U computer system. .\" .I Edit .\" is a variant of .\" .I ex .\" with features designed to .\" make it less complicated .\" to learn and use. .\" In terms of command syntax and effect .\" the editors are essentially identical, .\" and this command summary applies to both. .PP The summary is meant as a quick reference for users already acquainted with .\" .I edit .\" or .I ex . A fuller explanation of the editor is available in the document .\" .I .\" Edit: A Tutorial .\" .R .\" (a self-teaching introduction) and the .I Ex Reference Manual .R (the comprehensive reference source for .\" both \fIedit\fP and .I ex ). .\" Both of these writeups are available in the .\" Computing Services Library. .PP In the examples included with the summary, commands and text entered by the user are printed in \fBboldface\fR to distinguish them from responses printed by the computer. .sp 1v .LP .B The Editor Buffer .PP In order to perform its tasks the editor sets aside a temporary work space, called a \fIbuffer\fR, separate from the user's permanent file. Before starting to work on an existing file the editor makes a copy of it in the buffer, leaving the original untouched. All editing changes are made to the buffer copy, which must then be written back to the permanent file in order to update the old version. The buffer disappears at the end of the editing session. .sp 1v .LP .B Editing: Command and Text Input Modes .PP .R During an editing session there are two usual modes of operation: \fIcommand\fP mode and \fItext input\fP mode. (This disregards, for the moment, .\" .I open .\" and .I visual mode, discussed below.) In command mode, the editor issues a colon prompt (:) to show that it is ready to accept and execute a command. In text input mode, on the other hand, there is no prompt and the editor merely accepts text to be added to the buffer. Text input mode is initiated by the commands \fIappend\fP, \fIinsert\fP, and \fIchange\fP, and is terminated by typing a period as the first and only character on a line. .sp 1v .LP .B Line Numbers and Command Syntax .PP .R The editor keeps track of lines of text in the buffer by numbering them consecutively starting with 1 and renumbering as lines are added or deleted. At any given time the editor is positioned at one of these lines; this position is called the \fIcurrent line\fP. Generally, commands that change the contents of the buffer print the new current line at the end of their execution. .PP Most commands can be preceded by one or two line-number addresses which indicate the lines to be affected. If one number is given the command operates on that line only; if two, on an inclusive range of lines. Commands that can take line-number prefixes also assume default prefixes if none are given. The default assumed by each command is designed to make it convenient to use in many instances without any line-number prefix. For the most part, a command used without a prefix operates on the current line, though exceptions to this rule should be noted. The \fIprint\fP command by itself, for instance, causes one line, the current line, to be printed at the terminal. .PP The summary shows the number of line addresses that can be prefixed to each command as well as the defaults assumed if they are omitted. For example, .I (.,.) means that up to 2 line-numbers may be given, and that if none is given the command operates on the current line. (In the address prefix notation, ``.'' stands for the current line and ``$'' stands for the last line of the buffer.) If no such notation appears, no line-number prefix may be used. .PP Some commands take trailing information; only the more important instances of this are mentioned in the summary. .sp 1v .LP .B .\" Open and Visual Modes Visual Mode .PP .R Besides command and text input modes, .I ex .\" and .\" .I edit provides on some CRT terminals another mode of editing: .\" .I open .\" and .I visual . In this mode the cursor can be moved to individual words or characters in a line. The commands then given are very different from the standard editor commands; most do not appear on the screen when typed. .I An Introduction to Display Editing with Vi .R provides a full discussion. .sp 1v .LP .B Special Characters .PP .R .fi Some characters take on special meanings when used in context searches and in patterns given to the \fIsubstitute\fP command. .\" For \fIedit\fR, these are ``^'' and ``$'', .\" meaning the beginning and end of a line, .\" respectively. .I Ex has the following special characters: .sp 1v .B .ce 1 ^ $ \&. & * [ ] ~ .R .sp 1v To use one of the special characters as its simple graphic representation rather than with its special meaning, precede it by a backslash (\\). The backslash always has a special meaning. .sp 1v .1C .TS cp10 cp10 cp10 cp10 ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). Name Abbr Description Examples .sp 1.75 (.)\fBappend a T{ Begins text input mode, adding lines to the buffer after the line specified. Appending continues until ``.'' is typed alone at the beginning of a new line, followed by a carriage return. \fI0a\fR places lines at the beginning of the buffer. T} T{ .nf \fR:\fBa Three lines of text are added to the buffer after the current line. \*p .R \*c .fi T} .SP \fR(.,.)\fBchange c T{ Deletes indicated line(s) and initiates text input mode to replace them with new text which follows. New text is terminated the same way as with \fIappend\fR. T} T{ .nf :\fB5,6c Lines 5 and 6 are deleted and replaced by these three lines. \*p .R \*c .fi T} .SP \fR(.,.)\fBcopy \fIaddr co T{ Places a copy of the specified lines after the line indicated by \fIaddr\fR. The example places a copy of lines 8 through 12, inclusive, after line 25. T} T{ .nf \fR:\fB8,12co 25\fP Last line copied is printed \*c .fi T} .SP \fR(.,.)\fBdelete d T{ Removes lines from the buffer and prints the current line after the deletion. T} T{ .nf \fR:\fB13,15d\fP New current line is printed \*c .fi T} .TE .sp 0.5v .TS ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). T{ \fBedit \fIfile\fP .br \fBedit! \fIfile\fP T} T{ e .br e! T} T{ .fi \fRClears the editor buffer and then copies into it the named \fIfile\fR, which becomes the current file. This is a way of shifting to a different file without leaving the editor. The editor issues a warning message if this command is used before saving changes made to the file already in the buffer; using the form \fBe!\fR overrides this protective mechanism. T} T{ .nf \fR:\fBe ch10\fR No write since last change :\fBe! ch10\fR "ch10" 3 lines, 62 characters \*c .fi T} .SP \fBfile \fIname\fR f T{ \fRIf followed by a \fIname\fR, renames the current file to \fIname\fR. If used without \fIname\fR, prints the name of the current file. T} T{ .nf \fR:\fBf ch9\fP "ch9" [Modified] 3 lines ... :\fBf\fP "ch9" [Modified] 3 lines ... \*c .fi T} .SP (1,$)\fBglobal g \fBglobal/\fIpattern\fB/\fIcommands T{ .nf :\fBg/nonsense/d \fR\*c .fi T} \fR(1,$)\fBglobal! g!\fR or \fBv T{ Searches the entire buffer (unless a smaller range is specified by line-number prefixes) and executes \fIcommands\fR on every line with an expression matching \fIpattern\fR. The second form, abbreviated either \fBg!\fR or \fBv\fR, executes \fIcommands\fR on lines that \fIdo not\fR contain the expression \fIpattern\fR. T} \^ .SP \fR(.)\fBinsert i T{ Inserts new lines of text immediately before the specified line. Differs from .I append only in that text is placed before, rather than after, the indicated line. In other words, \fB1i\fR has the same effect as \fB0a\fR. T} T{ .nf :\fB1i These lines of text will be added prior to line 1. \&. \fR: .fi T} .SP \fR(.,.+1)\fBjoin j T{ Join lines together, adjusting whitespace (spaces and tabs) as necessary. T} T{ .nf :\fB2,5j\fR Resulting line is printed : .fi T} .TE .bp .TS cp10 cp10 cp10 cp10 ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). Name Abbr Description Examples .sp 1.75v \fR(.,.)\fBlist l T{ \fRPrints lines in a more unambiguous way than the \fIprint\fR command does. The end of a line, for example, is marked with a ``$'', and tabs printed as ``^I''. T} T{ .nf :\fB9l \fRThis is line 9$ \*c .fi T} .TE .sp 0.5v .TS ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). \fR(.,.)\fBmove \fIaddr\fB m T{ \fRMoves the specified lines to a position after the line indicated by \fIaddr\fR. T} T{ .nf \fR:\fB12,15m 25\fR New current line is printed \*c .fi T} .SP \fR(.,.)\fBnumber nu T{ Prints each line preceded by its buffer line number. T} T{ .nf \fR:\fBnu \0\0\fR10\0 This is line 10 \*c .fi T} .\" .SP .\" \fR(.)\fBopen o T{ .\" Too involved to discuss here, .\" but if you enter open mode .\" accidentally, press .\" the \s-2ESC\s0 key followed by .\" \fBq\fR to .\" get back into normal editor .\" command mode. .\" \fIEdit\fP is designed to .\" prevent accidental use of .\" the open command. .\" T} .SP \fBpreserve pre T{ Saves a copy of the current buffer contents as though the system had just crashed. This is for use in an emergency when a .I write command has failed and you don't know how else to save your work.* T} T{ .nf :\fBpreserve\fR File preserved. : .fi T} .SP \fR(.,.)\fBprint p Prints the text of line(s). T{ .nf :\fB+2,+3p\fR The second and third lines after the current line : .fi T} .TE .FS .ll 6.5i * You should seek assistance from a system administrator as soon as possible after saving a file with the .I preserve command, because the preserved copy of the file is saved in a directory used to store temporary files, and thus, the preserved copy may only be available for a short period of time. .FE .SP .nf .TS ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). T{ .nf \fBquit quit! .fi T} T{ .nf q q! T} T{ .fi \fREnds the editing session. You will receive a warning if you have changed the buffer since last writing its contents to the file. In this event you must either type \fBw\fR to write, or type \fBq!\fR to exit from the editor without saving your changes. T} T{ .nf \fR:\fBq \fRNo write since last change :\fBq! \fR% .fi T} .SP \fR(.)\fBread \fIfile\fP r T{ .fi \fRPlaces a copy of \fIfile\fR in the buffer after the specified line. Address 0 is permissible and causes the copy of \fIfile\fR to be placed at the beginning of the buffer. The \fIread\fP command does not erase any text already in the buffer. If no line number is specified, \fIfile\fR is placed after the current line. T} T{ .nf \fR:\fB0r newfile \fR"newfile" 5 lines, 86 characters \*c .fi T} .SP \fBrecover \fIfile\fP rec T{ .fi Retrieves a copy of the editor buffer after a system crash, editor crash, phone line disconnection, or \fIpreserve\fR command. T} .SP \fR(.,.)\fBsubstitute s T{ .nf \fBsubstitute/\fIpattern\fB/\fIreplacement\fB/ substitute/\fIpattern\fB/\fIreplacement\fB/gc .fi \fRReplaces the first occurrence of \fIpattern\fR on a line with \fIreplacement\fP. Including a \fBg\fR after the command changes all occurrences of \fIpattern\fP on the line. The \fBc\fR option allows the user to confirm each substitution before it is made; see the manual for details. T} T{ .nf :\fB3p \fRLine 3 contains a misstake :\fBs/misstake/mistake/ \fRLine 3 contains a mistake \*c .fi T} .TE .bp .TS cp10 cp10 cp10 cp10 ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). Name Abbr Description Examples .sp 1.75 \fBundo u T{ .fi \fRReverses the changes made in the buffer by the last buffer-editing command. Note that this example contains a notification about the number of lines affected. T} T{ .nf \fR:\fB1,15d \fR15 lines deleted new line number 1 is printed :\fBu \fR15 more lines in file ... old line number 1 is printed \*c .fi T} .SP \fR(1,$)\fBwrite \fIfile\fR w T{ .fi \fRCopies data from the buffer onto a permanent file. If no \fIfile\fR is named, the current filename is used. The file is automatically created if it does not yet exist. A response containing the number of lines and characters in the file indicates that the write has been completed successfully. The editor's built-in protections against overwriting existing files will in some circumstances inhibit a write. The form \fBw!\fR forces the write, confirming that an existing file is to be overwritten. T} T{ .nf \fR:\fBw \fR"file7" 64 lines, 1122 characters :\fBw file8 \fR"file8" File exists ... :\fBw! file8 \fR"file8" 64 lines, 1122 characters \*c .fi T} \fR(1,$)\fBwrite! \fIfile\fP w! \^ \^ .TE .sp 0.5v .TS ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). \fR(.)\fBz \fIcount\fP z T{ .fi \fRPrints a screen full of text starting with the line indicated; or, if \fIcount\fR is specified, prints that number of lines. Variants of the \fIz\fR command are described in the manual. T} .SP \fB!\fIcommand T{ .fi Executes the remainder of the line after \fB!\fR as a \*U command. The buffer is unchanged by this, and control is returned to the editor when the execution of \fIcommand\fR is complete. T} T{ .nf \fR:\fB!date \fRFri Jun 9 12:15:11 PDT 1978 ! \*c .fi T} .SP \fRcontrol-d T{ .fi Prints the next \fIscroll\fR of text, normally half of a screen. See the manual for details of the \fIscroll\fR option. T} .SP \fR(.+1) T{ .fi An address alone followed by a carriage return causes the line to be printed. A carriage return by itself prints the line following the current line. T} T{ .nf :\fR the line after the current line \*c .fi T} .TE .sp 0.5v .TS ltw(1.0i) lt2w(0.40i)fB ltw(3.0i) ltw(1.8i). \fB/\fIpattern\fB/ T{ .fi \fRSearches for the next line in which \fIpattern\fR occurs and prints it. T} T{ .nf \fR:\fB/This pattern/ \fRThis pattern next occurs here. \*c .fi T} .SP \fB// T{ Repeats the most recent search. T} T{ .nf \fR:\fB// \fRThis pattern also occurs here. \*c .fi T} .SP \fB?\fIpattern\fB? T{ Searches in the reverse direction for \fIpattern\fP. T} .SP \fB?? T{ Repeats the most recent search, moving in the reverse direction through the buffer. T} .TE ================================================ FILE: docs/USD.doc/re_format/vi_regex.7 ================================================ .\" $OpenBSD: re_format.7,v 1.23 2021/07/07 11:21:55 martijn Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1997, Phillip F Knaack .\" Copyright (c) 1992, 1993, 1994 Henry Spencer .\" Copyright (c) 1992, 1993, 1994 The Regents of the University of California .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" All rights reserved. .\" .\" This code is derived from software contributed to Berkeley by .\" Henry Spencer. .\" .\" 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. .\" .\" @(#)re_format.7 8.3 (Berkeley) 3/20/94 .\" .Dd $Mdocdate: July 7 2021 $ .Dt VI_REGEX 7 .Os .Sh NAME .Nm vi_regex .Nd POSIX regular expression support .Sh DESCRIPTION Regular expressions (REs), as defined in .St -p1003.1-2004 , come in two forms: basic regular expressions (BREs) and extended regular expressions (EREs). .Pp POSIX leaves some aspects of RE syntax and semantics open; .Sq ** marks decisions on these aspects that may not be fully portable to other POSIX implementations. .Pp This manual page first describes regular expressions in general, specifically extended regular expressions, and then discusses differences between them and basic regular expressions. .Sh EXTENDED REGULAR EXPRESSIONS An ERE is one** or more non-empty** .Em branches , separated by .Sq | . It matches anything that matches one of the branches. .Pp A branch is one** or more .Em pieces , concatenated. It matches a match for the first, followed by a match for the second, etc. .Pp A piece is an .Em atom possibly followed by a single** .Sq * , .Sq + , .Sq ?\& , or .Em bound . An atom followed by .Sq * matches a sequence of 0 or more matches of the atom. An atom followed by .Sq + matches a sequence of 1 or more matches of the atom. An atom followed by .Sq ?\& matches a sequence of 0 or 1 matches of the atom. .Pp A bound is .Sq { followed by an unsigned decimal integer, possibly followed by .Sq ,\& possibly followed by another unsigned decimal integer, always followed by .Sq } . The integers must lie between 0 and .Dv RE_DUP_MAX (255**) inclusive, and if there are two of them, the first may not exceed the second. An atom followed by a bound containing one integer .Ar i and no comma matches a sequence of exactly .Ar i matches of the atom. An atom followed by a bound containing one integer .Ar i and a comma matches a sequence of .Ar i or more matches of the atom. An atom followed by a bound containing two integers .Ar i and .Ar j matches a sequence of .Ar i through .Ar j (inclusive) matches of the atom. .Pp An atom is a regular expression enclosed in .Sq () (matching a part of the regular expression), an empty set of .Sq () (matching the null string)**, a .Em bracket expression (see below), .Sq .\& (matching any single character), .Sq ^ (matching the null string at the beginning of a line), .Sq $ (matching the null string at the end of a line), a .Sq \e followed by one of the characters .Sq ^.[$()|*+?{\e (matching that character taken as an ordinary character), a .Sq \e followed by any other character** (matching that character taken as an ordinary character, as if the .Sq \e had not been present**), or a single character with no other significance (matching that character). A .Sq { followed by a character other than a digit is an ordinary character, not the beginning of a bound**. It is illegal to end an RE with .Sq \e . .Pp A bracket expression is a list of characters enclosed in .Sq [] . It normally matches any single character from the list (but see below). If the list begins with .Sq ^ , it matches any single character .Em not from the rest of the list (but see below). If two characters in the list are separated by .Sq - , this is shorthand for the full .Em range of characters between those two (inclusive) in the collating sequence, e.g.\& .Sq [0-9] in ASCII matches any decimal digit. It is illegal** for two ranges to share an endpoint, e.g.\& .Sq a-c-e . Ranges are very collating-sequence-dependent, and portable programs should avoid relying on them. .Pp To include a literal .Sq ]\& in the list, make it the first character (following a possible .Sq ^ ) . To include a literal .Sq - , make it the first or last character, or the second endpoint of a range. To use a literal .Sq - as the first endpoint of a range, enclose it in .Sq [. and .Sq .] to make it a collating element (see below). With the exception of these and some combinations using .Sq \&[ (see next paragraphs), all other special characters, including .Sq \e , lose their special significance within a bracket expression. .Pp Within a bracket expression, a collating element (a character, a multi-character sequence that collates as if it were a single character, or a collating-sequence name for either) enclosed in .Sq [. and .Sq .] stands for the sequence of characters of that collating element. The sequence is a single element of the bracket expression's list. A bracket expression containing a multi-character collating element can thus match more than one character, e.g. if the collating sequence includes a .Sq ch collating element, then the RE .Sq [[.ch.]]*c matches the first five characters of .Sq chchcc . .Pp Within a bracket expression, a collating element enclosed in .Sq [= and .Sq =] is an equivalence class, standing for the sequences of characters of all collating elements equivalent to that one, including itself. (If there are no other equivalent collating elements, the treatment is as if the enclosing delimiters were .Sq [. and .Sq .] . ) For example, if .Sq x and .Sq y are the members of an equivalence class, then .Sq [[=x=]] , .Sq [[=y=]] , and .Sq [xy] are all synonymous. An equivalence class may not** be an endpoint of a range. .Pp Within a bracket expression, the name of a .Em character class enclosed in .Sq [: and .Sq :] stands for the list of all characters belonging to that class. Standard character class names are: .Bd -literal -offset indent alnum digit punct alpha graph space blank lower upper cntrl print xdigit .Ed .Pp These stand for the character classes defined in .Xr isalnum 3 , .Xr isalpha 3 , and so on. A character class may not be used as an endpoint of a range. .Pp There are two special cases** of bracket expressions: the bracket expressions .Sq [[:<:]] and .Sq [[:>:]] match the null string at the beginning and end of a word, respectively. A word is defined as a sequence of characters starting and ending with a word character which is neither preceded nor followed by word characters. A word character is an .Em alnum character (as defined by .Xr isalnum 3 ) or an underscore. This is an extension, compatible with but not specified by POSIX, and should be used with caution in software intended to be portable to other systems. The additional word delimiters .Ql \e< and .Ql \e> are provided to ease compatibility with traditional SVR4 systems but are not portable and should be avoided. .Pp In the event that an RE could match more than one substring of a given string, the RE matches the one starting earliest in the string. If the RE could match more than one substring starting at that point, it matches the longest. Subexpressions also match the longest possible substrings, subject to the constraint that the whole match be as long as possible, with subexpressions starting earlier in the RE taking priority over ones starting later. Note that higher-level subexpressions thus take priority over their lower-level component subexpressions. .Pp Match lengths are measured in characters, not collating elements. A null string is considered longer than no match at all. For example, .Sq bb* matches the three middle characters of .Sq abbbc ; .Sq (wee|week)(knights|nights) matches all ten characters of .Sq weeknights ; when .Sq (.*).* is matched against .Sq abc , the parenthesized subexpression matches all three characters; and when .Sq (a*)* is matched against .Sq bc , both the whole RE and the parenthesized subexpression match the null string. .Pp If case-independent matching is specified, the effect is much as if all case distinctions had vanished from the alphabet. When an alphabetic that exists in multiple cases appears as an ordinary character outside a bracket expression, it is effectively transformed into a bracket expression containing both cases, e.g.\& .Sq x becomes .Sq [xX] . When it appears inside a bracket expression, all case counterparts of it are added to the bracket expression, so that, for example, .Sq [x] becomes .Sq [xX] and .Sq [^x] becomes .Sq [^xX] . .Pp No particular limit is imposed on the length of REs**. Programs intended to be portable should not employ REs longer than 256 bytes, as an implementation can refuse to accept such REs and remain POSIX-compliant. .Pp The following is a list of extended regular expressions: .Bl -tag -width Ds .It Ar c Any character .Ar c not listed below matches itself. .It \e Ns Ar c Any backslash-escaped character .Ar c matches itself. .It \&. Matches any single character that is not a newline .Pq Sq \en . .It Bq Ar char-class Matches any single character in .Ar char-class . To include a .Ql \&] in .Ar char-class , it must be the first character. A range of characters may be specified by separating the end characters of the range with a .Ql - ; e.g.\& .Ar a-z specifies the lower case characters. The following literal expressions can also be used in .Ar char-class to specify sets of characters: .Bd -unfilled -offset indent [:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:] [:digit:] [:print:] [:upper:] [:blank:] [:graph:] [:punct:] [:xdigit:] .Ed .Pp If .Ql - appears as the first or last character of .Ar char-class , then it matches itself. All other characters in .Ar char-class match themselves. .Pp Patterns in .Ar char-class of the form .Eo [. .Ar col-elm .Ec .]\& or .Eo [= .Ar col-elm .Ec =]\& , where .Ar col-elm is a collating element, are interpreted according to .Xr setlocale 3 .Pq not currently supported . .It Bq ^ Ns Ar char-class Matches any single character, other than newline, not in .Ar char-class . .Ar char-class is defined as above. .It ^ If .Sq ^ is the first character of a regular expression, then it anchors the regular expression to the beginning of a line. Otherwise, it matches itself. .It $ If .Sq $ is the last character of a regular expression, it anchors the regular expression to the end of a line. Otherwise, it matches itself. .It [[:<:]] Anchors the single character regular expression or subexpression immediately following it to the beginning of a word. .It [[:>:]] Anchors the single character regular expression or subexpression immediately preceding it to the end of a word. .It Pq Ar re Defines a subexpression .Ar re . Any set of characters enclosed in parentheses matches whatever the set of characters without parentheses matches (that is a long-winded way of saying the constructs .Sq (re) and .Sq re match identically). .It * Matches the single character regular expression or subexpression immediately preceding it zero or more times. If .Sq * is the first character of a regular expression or subexpression, then it matches itself. The .Sq * operator sometimes yields unexpected results. For example, the regular expression .Ar b* matches the beginning of the string .Qq abbb (as opposed to the substring .Qq bbb ) , since a null match is the only leftmost match. .It + Matches the singular character regular expression or subexpression immediately preceding it one or more times. .It ? Matches the singular character regular expression or subexpression immediately preceding it 0 or 1 times. .Sm off .It Xo .Pf { Ar n , m No }\ \& .Pf { Ar n , No }\ \& .Pf { Ar n No } .Xc .Sm on Matches the single character regular expression or subexpression immediately preceding it at least .Ar n and at most .Ar m times. If .Ar m is omitted, then it matches at least .Ar n times. If the comma is also omitted, then it matches exactly .Ar n times. .It | Used to separate patterns. For example, the pattern .Sq cat|dog matches either .Sq cat or .Sq dog . .El .Sh BASIC REGULAR EXPRESSIONS Basic regular expressions differ in several respects: .Bl -bullet -offset 3n .It The delimiters for bounds are .Sq \e{ and .Sq \e} , with .Sq { and .Sq } by themselves ordinary characters. .It .Sq | , .Sq + , and .Sq ?\& are ordinary characters. .Sq \e{1,\e} is equivalent to .Sq + . .Sq \e{0,1\e} is equivalent to .Sq ?\& . There is no equivalent for .Sq | . .It The parentheses for nested subexpressions are .Sq \e( and .Sq \e) , with .Sq \&( and .Sq )\& by themselves ordinary characters. .It .Sq ^ is an ordinary character except at the beginning of the RE or** the beginning of a parenthesized subexpression. .It .Sq $ is an ordinary character except at the end of the RE or** the end of a parenthesized subexpression. .It .Sq * is an ordinary character if it appears at the beginning of the RE or the beginning of a parenthesized subexpression (after a possible leading .Sq ^ ) . .It Finally, there is one new type of atom, a .Em back-reference : .Sq \e followed by a non-zero decimal digit .Ar d matches the same sequence of characters matched by the .Ar d Ns th parenthesized subexpression (numbering subexpressions by the positions of their opening parentheses, left to right), so that, for example, .Sq \e([bc]\e)\e1 matches .Sq bb\& or .Sq cc but not .Sq bc . .El .Pp The following is a list of basic regular expressions: .Bl -tag -width Ds .It Ar c Any character .Ar c not listed below matches itself. .It \e Ns Ar c Any backslash-escaped character .Ar c , except for .Sq { , .Sq } , .Sq \&( , and .Sq \&) , matches itself. .It \&. Matches any single character that is not a newline .Pq Sq \en . .It Bq Ar char-class Matches any single character in .Ar char-class . To include a .Ql \&] in .Ar char-class , it must be the first character. A range of characters may be specified by separating the end characters of the range with a .Ql - ; e.g.\& .Ar a-z specifies the lower case characters. The following literal expressions can also be used in .Ar char-class to specify sets of characters: .Bd -unfilled -offset indent [:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:] [:digit:] [:print:] [:upper:] [:blank:] [:graph:] [:punct:] [:xdigit:] .Ed .Pp If .Ql - appears as the first or last character of .Ar char-class , then it matches itself. All other characters in .Ar char-class match themselves. .Pp Patterns in .Ar char-class of the form .Eo [. .Ar col-elm .Ec .]\& or .Eo [= .Ar col-elm .Ec =]\& , where .Ar col-elm is a collating element, are interpreted according to .Xr setlocale 3 .Pq not currently supported . .It Bq ^ Ns Ar char-class Matches any single character, other than newline, not in .Ar char-class . .Ar char-class is defined as above. .It ^ If .Sq ^ is the first character of a regular expression, then it anchors the regular expression to the beginning of a line. Otherwise, it matches itself. .It $ If .Sq $ is the last character of a regular expression, it anchors the regular expression to the end of a line. Otherwise, it matches itself. .It [[:<:]] Anchors the single character regular expression or subexpression immediately following it to the beginning of a word. .It [[:>:]] Anchors the single character regular expression or subexpression immediately following it to the end of a word. .It \e( Ns Ar re Ns \e) Defines a subexpression .Ar re . Subexpressions may be nested. A subsequent backreference of the form .Pf \e Ar n , where .Ar n is a number in the range [1,9], expands to the text matched by the .Ar n Ns th subexpression. For example, the regular expression .Ar \e(.*\e)\e1 matches any string consisting of identical adjacent substrings. Subexpressions are ordered relative to their left delimiter. .It * Matches the single character regular expression or subexpression immediately preceding it zero or more times. If .Sq * is the first character of a regular expression or subexpression, then it matches itself. The .Sq * operator sometimes yields unexpected results. For example, the regular expression .Ar b* matches the beginning of the string .Qq abbb (as opposed to the substring .Qq bbb ) , since a null match is the only leftmost match. .Sm off .It Xo .Pf \e{ Ar n , m No \e}\ \& .Pf \e{ Ar n , No \e}\ \& .Pf \e{ Ar n No \e} .Xc .Sm on Matches the single character regular expression or subexpression immediately preceding it at least .Ar n and at most .Ar m times. If .Ar m is omitted, then it matches at least .Ar n times. If the comma is also omitted, then it matches exactly .Ar n times. .El .Sh SEE ALSO .Xr regex 3 .Sh STANDARDS .St -p1003.1-2004 : Base Definitions, Chapter 9 (Regular Expressions). .Sh BUGS Having two kinds of REs is a botch. .Pp The current POSIX spec says that .Sq )\& is an ordinary character in the absence of an unmatched .Sq \&( ; this was an unintentional result of a wording error, and change is likely. Avoid relying on it. .Pp Back-references are a dreadful botch, posing major problems for efficient implementations. They are also somewhat vaguely defined (does .Sq a\e(\e(b\e)*\e2\e)*d match .Sq abbbd ? ) . Avoid using them. .Pp POSIX's specification of case-independent matching is vague. The .Dq one case implies all cases definition given above is the current consensus among implementers as to the right interpretation. .Pp The syntax for word boundaries is incredibly ugly. ================================================ FILE: docs/USD.doc/vi.man/vi.1 ================================================ .\" $OpenBSD: vi.1,v 1.83 2023/01/29 09:28:57 otto Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1994 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 1994, 1995, 1996 .\" Keith Bostic. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" The vi program is freely redistributable. .\" You are welcome to copy, modify and share it with others .\" under the conditions listed in the LICENSE.md file. .\" .\" If any company (not individual!) finds vi sufficiently useful .\" that you would have purchased it, or if any company wishes to .\" redistribute it, contributions to the authors would be appreciated. .\" .\" @(#)vi.1 8.51 (Berkeley) 10/10/96 .\" .Dd $Mdocdate: January 29 2023 $ .Dt VI 1 .Os .Sh NAME .Nm ex , vi , view .Nd text editors .Sh SYNOPSIS .Nm ex .Op Fl FRrSsv .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Nm vi\ \& .Op Fl eFRrS .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Nm view .Op Fl eFrS .Op Fl c Ar cmd .Op Fl t Ar tag .Op Fl w Ar size .Op Ar .Sh DESCRIPTION .Nm ex is a line-oriented text editor; .Nm vi is a screen-oriented text editor. .Nm ex and .Nm vi are different interfaces to the same program, and it is possible to switch back and forth during an edit session. .Nm view is the equivalent of using the .Fl R .Pq read-only option of .Nm vi . .Pp This manual page is the one provided with the .Nm oex Ns / Ns Nm ovi versions of the .Nm oex Ns / Ns Nm vi text editors. .Nm oex Ns / Ns Nm ovi are intended as enhanced but compatible replacements for the original Fourth Berkeley Software Distribution .Pq 4BSD .Nm ex and .Nm vi programs. For the rest of this manual page, .Nm oex Ns / Ns Nm ovi is used only when it's necessary to distinguish it from the historic implementations of .Nm ex Ns / Ns Nm vi . .Pp This manual page is intended for users already familiar with .Nm ex Ns / Ns Nm vi . Anyone else should almost certainly read a good tutorial on the editor before this manual page. If you're in an unfamiliar environment, and you absolutely have to get work done immediately, read the section after the options description, entitled .Sx FAST STARTUP . It's probably enough to get you going. .Pp The following options are available: .Bl -tag -width "-w size " .It Fl c Ar cmd Execute .Ar cmd on the first file loaded. Particularly useful for initial positioning in the file, although .Ar cmd is not limited to positioning commands. This is the POSIX 1003.2 interface for the historic .Dq +cmd syntax. .Nm oex Ns / Ns Nm ovi supports both the old and new syntax. This option is mutally exclusive with the .Dq -C option. .It Fl C Ar cmd Execute .Ar cmd when starting, like using .Dq -c but the command is executed even if a file is not loaded, just after processing any startup commands from the .Pa $HOME/.nexrc or .Pa $HOME/.exrc files. This option is mutually exclusive with the .Dq -c option. .It Fl e Start editing in ex mode, as if the command name were .Nm ex . .It Fl F Don't copy the entire file when first starting to edit. (The default is to make a copy in case someone else modifies the file during your edit session.) .It Fl R Start editing in read-only mode, as if the command name was .Nm view , or the .Cm readonly option was set. .It Fl r Recover the specified files or, if no files are specified, list the files that could be recovered. If no recoverable files by the specified name exist, the file is edited as if the .Fl r option had not been specified. .It Fl S Run with the .Cm secure edit option set, disallowing all access to external programs. .It Fl s Enter batch mode; applicable only to .Nm ex edit sessions. Batch mode is useful when running .Nm ex scripts. Prompts, informative messages and other user oriented messages are turned off, and no startup files or environment variables are read. This is the POSIX 1003.2 interface for the historic .Dq - argument. .Nm oex Ns / Ns Nm ovi supports both the old and new syntax. .It Fl t Ar tag Start editing at the specified .Ar tag (see .Xr ctags 1 ) . .It Fl v Start editing in vi mode, as if the command name was .Nm vi . .It Fl w Ar size Set the initial window size to the specified number of lines. .El .Pp Command input for .Nm ex Ns / Ns Nm vi is read from the standard input. In the .Nm vi interface, it is an error if standard input is not a terminal. In the .Nm ex interface, if standard input is not a terminal, .Nm ex will read commands from it regardless; however, the session will be a batch mode session, exactly as if the .Fl s option had been specified. .Sh FAST STARTUP This section will tell you the minimum amount that you need to do simple editing tasks using .Nm vi . If you've never used any screen editor before, you're likely to have problems even with this simple introduction. In that case you should find someone that already knows .Nm vi and have them walk you through this section. .Pp .Nm vi is a screen editor. This means that it takes up almost the entire screen, displaying part of the file on each screen line, except for the last line of the screen. The last line of the screen is used for you to give commands to .Nm vi , and for .Nm vi to give information to you. .Pp The other fact that you need to understand is that .Nm vi is a modeful editor, i.e. you are either entering text or you are executing commands, and you have to be in the right mode to do one or the other. You will be in command mode when you first start editing a file. There are commands that switch you into input mode. There is only one key that takes you out of input mode, and that is the .Aq escape key. .Pp Key names are written using angle brackets, e.g.\& .Aq escape means the .Dq escape key, usually labeled .Dq Esc on your terminal's keyboard. If you're ever confused as to which mode you're in, keep entering the .Aq escape key until .Nm vi beeps at you. Generally, .Nm vi will beep at you if you try and do something that's not allowed. It will also display error messages. .Pp To start editing a file, enter the following command: .Pp .Dl $ vi file .Pp The command you should enter as soon as you start editing is: .Pp .Dl :set verbose showmode .Pp This will make the editor give you verbose error messages and display the current mode at the bottom of the screen. .Pp The commands to move around the file are: .Bl -tag -width Ds .It Cm h Move the cursor left one character. .It Cm j Move the cursor down one line. .It Cm k Move the cursor up one line. .It Cm l Move the cursor right one character. .It Aq Cm cursor-arrows The cursor arrow keys should work, too. .It Cm / Ns text Search for the string .Dq text in the file, and move the cursor to its first character. .El .Pp The commands to enter new text are: .Bl -tag -width "" .It Cm a Append new text, after the cursor. .It Cm i Insert new text, before the cursor. .It Cm O Open a new line above the line the cursor is on, and start entering text. .It Cm o Open a new line below the line the cursor is on, and start entering text. .It Aq Cm escape Once you've entered input mode using one of the .Cm a , .Cm i , .Cm O or .Cm o commands, use .Aq Cm escape to quit entering text and return to command mode. .El .Pp The commands to copy text are: .Bl -tag -width Ds .It Cm p Append the copied line after the line the cursor is on. .It Cm yy Copy the line the cursor is on. .El .Pp The commands to delete text are: .Bl -tag -width Ds .It Cm dd Delete the line the cursor is on. .It Cm x Delete the character the cursor is on. .El .Pp The commands to write the file are: .Bl -tag -width Ds .It Cm :w Write the file back to the file with the name that you originally used as an argument on the .Nm vi command line. .It Cm :w Ar file_name Write the file back to the file with the name .Ar file_name . .El .Pp The commands to quit editing and exit the editor are: .Bl -tag -width Ds .It Cm :q Quit editing and leave .Nm vi (if you've modified the file, but not saved your changes, .Nm vi will refuse to quit). .It Cm :q! Quit, discarding any modifications that you may have made. .El .Pp One final caution: Unusual characters can take up more than one column on the screen, and long lines can take up more than a single screen line. The above commands work on .Dq physical characters and lines, i.e. they affect the entire line no matter how many screen lines it takes up and the entire character no matter how many screen columns it takes up. .Sh REGULAR EXPRESSIONS .Nm ex Ns / Ns Nm vi supports regular expressions .Pq REs , as documented in .Xr re_format 7 , for line addresses, as the first part of the .Nm ex Cm substitute , .Cm global and .Cm v commands, and in search patterns. Basic regular expressions .Pq BREs are enabled by default; extended regular expressions .Pq EREs are used if the .Cm extended option is enabled. The use of regular expressions can be largely disabled using the .Cm magic option. .Pp The following strings have special meanings in the .Nm ex Ns / Ns Nm vi version of regular expressions: .Bl -bullet -offset 6u .It An empty regular expression is equivalent to the last regular expression used. .It .Sq \e< matches the beginning of the word. .It .Sq \e> matches the end of the word. .It .Sq ~ matches the replacement part of the last .Cm s command. .El .Sh BUFFERS A buffer is an area where commands can save changed or deleted text for later use. .Nm vi buffers are named with a single character preceded by a double quote, for example .Pf \&" Aq c ; .Nm ex buffers are the same, but without the double quote. .Nm oex Ns / Ns Nm ovi permits the use of any character without another meaning in the position where a buffer name is expected. .Pp All buffers are either in .Em line mode or .Em character mode . Inserting a buffer in line mode into the text creates new lines for each of the lines it contains, while a buffer in character mode creates new lines for any lines .Em other than the first and last lines it contains. The first and last lines are inserted at the current cursor position, becoming part of the current line. If there is more than one line in the buffer, the current line itself will be split. All .Nm ex commands which store text into buffers do so in line mode. The behaviour of .Nm vi commands depend on their associated motion command: .Bl -bullet -offset 6u .It .Aq Cm control-A , .Cm h , .Cm l , .Cm ,\& , .Cm 0 , .Cm B , .Cm E , .Cm F , .Cm T , .Cm W , .Cm ^ , .Cm b , .Cm e , .Cm f and .Cm t make the destination buffer character-oriented. .It .Cm j , .Aq Cm control-M , .Cm k , .Cm ' , .Cm - , .Cm G , .Cm H , .Cm L , .Cm M , .Cm _ and .Cm |\& make the destination buffer line-oriented. .It .Cm $ , .Cm % , .Cm ` , .Cm (\& , .Cm )\& , .Cm / , .Cm ?\& , .Cm [[ , .Cm ]] , .Cm { and .Cm } make the destination buffer character-oriented, unless the starting and end positions are the first and last characters on a line. In that case, the buffer is line-oriented. .El .Pp The .Nm ex command .Cm display buffers displays the current mode for each buffer. .Pp Buffers named .Sq a through .Sq z may be referred to using their uppercase equivalent, in which case new content will be appended to the buffer, instead of replacing it. .Pp Buffers named .Sq 1 through .Sq 9 are special. A region of text modified using the .Cm c .Pq change or .Cm d .Pq delete commands is placed into the numeric buffer .Sq 1 if no other buffer is specified and if it meets one of the following conditions: .Bl -bullet -offset 6u .It It includes characters from more than one line. .It It is specified using a line-oriented motion. .It It is specified using one of the following motion commands: .Aq Cm control-A , .Cm ` Ns Aq Cm character , .Cm n , .Cm N , .Cm % , .Cm / , .Cm { , .Cm } , .Cm \&( , .Cm \&) , and .Cm \&? . .El .Pp Before this copy is done, the previous contents of buffer .Sq 1 are moved into buffer .Sq 2 , .Sq 2 into buffer .Sq 3 , and so on. The contents of buffer .Sq 9 are discarded. Note that this rotation occurs .Em regardless of the user specifying another buffer. In .Nm vi , text may be explicitly stored into the numeric buffers. In this case, the buffer rotation occurs before the replacement of the buffer's contents. The numeric buffers are only available in .Nm vi mode. .Sh VI COMMANDS The following section describes the commands available in the command mode of the .Nm vi editor. The following words have a special meaning in the commands description: .Pp .Bl -tag -width bigword -compact -offset 3u .It Ar bigword A set of non-whitespace characters. .It Ar buffer Temporary area where commands may place text. If not specified, the default buffer is used. See also .Sx BUFFERS , above. .It Ar count A positive number used to specify the desired number of iterations of a command. It defaults to 1 if not specified. .It Ar motion A cursor movement command which indicates the other end of the affected region of text, the first being the current cursor position. Repeating the command character makes it affect the whole current line. .It Ar word A sequence of letters, digits or underscores. .El .Pp .Ar buffer and .Ar count , if both present, may be specified in any order. .Ar motion and .Ar count , if both present, are effectively multiplied together and considered part of the motion. .Pp .Bl -tag -width Ds -compact .It Xo .Aq Cm control-A .Xc Search forward for the word starting at the cursor position. .Pp .It Xo .Op Ar count .Aq Cm control-B .Xc Page backwards .Ar count screens. Two lines of overlap are maintained, if possible. .Pp .It Xo .Op Ar count .Aq Cm control-D .Xc Scroll forward .Ar count lines. If .Ar count is not given, scroll forward the number of lines specified by the last .Aq Cm control-D or .Aq Cm control-U command. If this is the first .Aq Cm control-D command, scroll half the number of lines in the current screen. .Pp .It Xo .Op Ar count .Aq Cm control-E .Xc Scroll forward .Ar count lines, leaving the current line and column as is, if possible. .Pp .It Xo .Op Ar count .Aq Cm control-F .Xc Page forward .Ar count screens. Two lines of overlap are maintained, if possible. .Pp .It Aq Cm control-G Display the following file information: the file name (as given to .Nm vi ) ; whether the file has been modified since it was last written; if the file is readonly; the current line number; the total number of lines in the file; and the current line number as a percentage of the total lines in the file. .Pp .It Xo .Op Ar count .Aq Cm control-H .Xc .It Xo .Op Ar count .Cm h .Xc Move the cursor back .Ar count characters in the current line. .Pp .It Xo .Op Ar count .Aq Cm control-J .Xc .It Xo .Op Ar count .Aq Cm control-N .Xc .It Xo .Op Ar count .Cm j .Xc Move the cursor down .Ar count lines without changing the current column. .Pp .It Aq Cm control-L .It Aq Cm control-R Repaint the screen. .Pp .It Xo .Op Ar count .Aq Cm control-M .Xc .It Xo .Op Ar count .Cm + .Xc Move the cursor down .Ar count lines to the first non-blank character of that line. .Pp .It Xo .Op Ar count .Aq Cm control-P .Xc .It Xo .Op Ar count .Cm k .Xc Move the cursor up .Ar count lines, without changing the current column. .Pp .It Aq Cm control-T Return to the most recent tag context. .Pp .It Xo .Op Ar count .Aq Cm control-U .Xc Scroll backwards .Ar count lines. If .Ar count is not given, scroll backwards the number of lines specified by the last .Aq Cm control-D or .Aq Cm control-U command. If this is the first .Aq Cm control-U command, scroll half the number of lines in the current screen. .Pp .It Aq Cm control-W Switch to the next lower screen in the window, or to the first screen if there are no lower screens in the window. .Pp .It Xo .Op Ar count .Aq Cm control-Y .Xc Scroll backwards .Ar count lines, leaving the current line and column as is, if possible. .Pp .It Aq Cm control-Z Suspend the current editor session. .Pp .It Aq Cm escape Execute the .Nm ex command being entered, or cancel it if it is only partial. .Pp .It Aq Cm control-] Push a tag reference onto the tag stack. .Pp .It Aq Cm control-^ Switch to the most recently edited file. .Pp .It Xo .Op Ar count .Aq Cm space .Xc .It Xo .Op Ar count .Cm l .Xc Move the cursor forward .Ar count characters without changing the current line. .Pp .It Xo .Op Ar count .Cm !\& .Ar motion shell-argument(s) .Aq Li carriage-return .Xc Replace the lines spanned by .Ar count and .Ar motion with the output .Pq standard output and standard error of the program named by the .Cm shell option, called with a .Fl c flag followed by the .Ar shell-argument(s) .Pq bundled into a single argument . Within .Ar shell-argument(s) , the .Sq % , .Sq # and .Sq !\& characters are expanded to the current file name, the previous current file name, and the command text of the previous .Cm !\& or .Cm :! commands, respectively. The special meaning of .Sq % , .Sq # and .Sq !\& can be overridden by escaping them with a backslash. .Pp .It Xo .Op Ar count .Cm # .Sm off .Cm # | + | - .Sm on .Xc Increment .Pq trailing So # Sc or So + Sc or decrement .Pq trailing Sq - the number under the cursor by .Ar count , starting at the cursor position or at the first non-blank character following it. Numbers with a leading .Sq 0x or .Sq 0X are interpreted as hexadecimal numbers. Numbers with a leading .Sq 0 are interpreted as octal numbers unless they contain a non-octal digit. Other numbers may be prefixed with a .Sq + or .Sq - sign. .Pp .It Xo .Op Ar count .Cm $ .Xc Move the cursor to the end of a line. If .Ar count is specified, additionally move the cursor down .Ar count \- 1 lines. .Pp .It Cm % Move to the parenthesis, square bracket or curly brace matching the one found at the cursor position or the closest to the right of it. .Pp .It Cm & Repeat the previous substitution command on the current line. .Pp .It Xo .Cm ' Ns Aq Ar character .Xc .It Xo .Cm ` Ns Aq Ar character .Xc Return to the cursor position marked by the character .Ar character , or, if .Ar character is .Sq ' or .Sq ` , to the position of the cursor before the last of the following commands: .Aq Cm control-A , .Aq Cm control-T , .Aq Cm control-] , .Cm % , .Cm ' , .Cm ` , .Cm (\& , .Cm )\& , .Cm / , .Cm ?\& , .Cm G , .Cm H , .Cm L , .Cm [[ , .Cm ]] , .Cm { , .Cm } . The first form returns to the first non-blank character of the line marked by .Ar character . The second form returns to the line and column marked by .Ar character . .Pp .It Xo .Op Ar count .Cm \&( .Xc .It Xo .Op Ar count .Cm \&) .Xc Move .Ar count sentences backward or forward, respectively. A sentence is an area of text that begins with the first nonblank character following the previous sentence, paragraph, or section boundary and continues until the next period, exclamation mark, or question mark character, followed by any number of closing parentheses, brackets, double or single quote characters, followed by either an end-of-line or two whitespace characters. Groups of empty lines .Pq or lines containing only whitespace characters are treated as a single sentence. .Pp .It Xo .Op Ar count .Cm ,\& .Xc Reverse find character (i.e. the last .Cm F , .Cm f , .Cm T or .Cm t command) .Ar count times. .Pp .It Xo .Op Ar count .Cm - .Xc Move to the first non-blank character of the previous line, .Ar count times. .Pp .It Xo .Op Ar count .Cm .\& .Xc Repeat the last .Nm vi command that modified text. .Ar count replaces both the .Ar count argument of the repeated command and that of the associated .Ar motion . If the .Cm .\& command repeats the .Cm u command, the change log is rolled forward or backward, depending on the action of the .Cm u command. .Pp .It Xo .Pf / Ar RE .Aq Li carriage-return .Xc .It Xo .Pf / Ar RE Ns / .Op Ar offset .Op Cm z .Aq Li carriage-return .Xc .It Xo .Pf ? Ar RE .Aq Li carriage-return .Xc .It Xo .Pf ? Ar RE ? Op Ar offset .Op Cm z .Aq Li carriage-return .Xc .It Cm N .It Cm n Search forward .Pq Sq / or backward .Pq Sq ?\& for a regular expression. .Cm n and .Cm N repeat the last search in the same or opposite directions, respectively. If .Ar RE is empty, the last search regular expression is used. If .Ar offset is specified, the cursor is placed .Ar offset lines before or after the matched regular expression. If either .Cm n or .Cm N commands are used as motion components for the .Cm !\& command, there will be no prompt for the text of the command and the previous .Cm !\& will be executed. Multiple search patterns may be grouped together by delimiting them with semicolons and zero or more whitespace characters. These patterns are evaluated from left to right with the final cursor position determined by the last search pattern. A .Cm z command may be appended to the closed search expressions to reposition the result line. .Pp .It Cm 0 Move to the first character in the current line. .Pp .It Cm :\& Execute an .Nm ex command. .Pp .It Xo .Op Ar count .Cm ;\& .Xc Repeat the last character find (i.e. the last .Cm F , .Cm f , .Cm T or .Cm t command) .Ar count times. .Pp .It Xo .Op Ar count .Cm < .Ar motion .Xc .It Xo .Op Ar count .Cm > .Ar motion .Xc Shift .Ar count lines left or right, respectively, by an amount of .Cm shiftwidth . .Pp .It Cm @ Ar buffer Execute a named .Ar buffer as .Nm vi commands. The buffer may include .Nm ex commands too, but they must be expressed as a .Cm \&: command. If .Ar buffer is .Sq @ or .Sq * , then the last buffer executed shall be used. .Pp .It Xo .Op Ar count .Cm A .Xc Enter input mode, appending the text after the end of the line. If a .Ar count argument is given, the characters input are repeated .Ar count \- 1 times after input mode is exited. .Pp .It Xo .Op Ar count .Cm B .Xc Move backwards .Ar count bigwords. .Pp .It Xo .Op Ar buffer .Cm C .Xc Change text from the current position to the end-of-line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Cm D .Xc Delete text from the current position to the end-of-line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm E .Xc Move forward .Ar count end-of-bigwords. .Pp .It Xo .Op Ar count .Cm F Aq Ar character .Xc Search .Ar count times backward through the current line for .Ar character . .Pp .It Xo .Op Ar count .Cm G .Xc Move to line .Ar count , or the last line of the file if .Ar count is not specified. .Pp .It Xo .Op Ar count .Cm H .Xc Move to the screen line .Ar count \- 1 lines below the top of the screen. .Pp .It Xo .Op Ar count .Cm I .Xc Enter input mode, inserting the text at the beginning of the line. If a .Ar count argument is given, the characters input are repeated .Ar count \- 1 more times. .Pp .It Xo .Op Ar count .Cm J .Xc Join .Ar count lines with the current line. The spacing between two joined lines is set to two whitespace characters if the former ends with a question mark, a period or an exclamation mark. It is set to one whitespace character otherwise. .Pp .It Xo .Op Ar count .Cm L .Xc Move to the screen line .Ar count \- 1 lines above the bottom of the screen. .Pp .It Cm M Move to the screen line in the middle of the screen. .Pp .It Xo .Op Ar count .Cm O .Xc Enter input mode, appending text in a new line above the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \- 1 more times. .Pp .It Xo .Op Ar buffer .Cm P .Xc Insert text from .Ar buffer before the current column if .Ar buffer is character-oriented or before the current line if it is line-oriented. .Pp .It Cm Q Exit .Nm vi .Pq or visual mode and switch to .Nm ex mode. .Pp .It Xo .Op Ar count .Cm R .Xc Enter input mode, replacing the characters in the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \- 1 more times upon exit from insert mode. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm S .Xc Substitute .Ar count lines. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm T .Aq Ar character .Xc Search backwards, .Ar count times, through the current line for the character after the specified .Ar character . .Pp .It Cm U Restore the current line to its state before the cursor last moved to it. .Pp .It Xo .Op Ar count .Cm W .Xc Move forward .Ar count bigwords. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm X .Xc Delete .Ar count characters before the cursor, on the current line. If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm Y .Xc Copy (or .Dq yank ) .Ar count lines into .Ar buffer . .Pp .It Cm ZZ Write the file and exit .Nm vi if there are no more files to edit. Entering two .Dq quit commands in a row ignores any remaining file to edit. .Pp .It Xo .Op Ar count .Cm [[ .Xc Back up .Ar count section boundaries. .Pp .It Xo .Op Ar count .Cm ]] .Xc Move forward .Ar count section boundaries. .Pp .It Cm ^ Move to the first non-blank character on the current line. .Pp .It Xo .Op Ar count .Cm _ .Xc Move down .Ar count \- 1 lines, to the first non-blank character. .Pp .It Xo .Op Ar count .Cm a .Xc Enter input mode, appending the text after the cursor. If a .Ar count argument is given, the characters input are repeated .Ar count \-1 more times. .Pp .It Xo .Op Ar count .Cm b .Xc Move backwards .Ar count words. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm c .Ar motion .Xc Change the region of text described by .Ar count and .Ar motion . If .Ar buffer is specified, .Dq yank the changed text into .Ar buffer . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm d .Ar motion .Xc Delete the region of text described by .Ar count and .Ar motion . If .Ar buffer is specified, .Dq yank the deleted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm e .Xc Move forward .Ar count end-of-words. .Pp .It Xo .Op Ar count .Cm f Aq Ar character .Xc Search forward, .Ar count times, through the rest of the current line for .Aq Ar character . .Pp .It Xo .Op Ar count .Cm i .Xc Enter input mode, inserting the text before the cursor. If a .Ar count argument is given, the characters input are repeated .Ar count \-1 more times. .Pp .It Xo .Cm m .Aq Ar character .Xc Save the current context .Pq line and column as .Aq Ar character . .Pp .It Xo .Op Ar count .Cm o .Xc Enter input mode, appending text in a new line under the current line. If a .Ar count argument is given, the characters input are repeated .Ar count \- 1 more times. .Pp .It Xo .Op Ar buffer .Cm p .Xc Append text from .Ar buffer . Text is appended after the current column if .Ar buffer is character oriented, or the after current line otherwise. .Pp .It Xo .Op Ar count .Cm r .Aq Ar character .Xc Replace .Ar count characters by .Ar character . .Pp .It Xo .Op Ar buffer .Op Ar count .Cm s .Xc Substitute .Ar count characters in the current line starting with the current character. If .Ar buffer is specified, .Dq yank the substituted text into .Ar buffer . .Pp .It Xo .Op Ar count .Cm t .Aq Ar character .Xc Search forward, .Ar count times, through the current line for the character immediately before .Aq Ar character . .Pp .It Cm u Undo the last change made to the file. If repeated, the .Cm u command alternates between these two states. The .Cm .\& command, when used immediately after .Cm u , causes the change log to be rolled forward or backward, depending on the action of the .Cm u command. .Pp .It Xo .Op Ar count .Cm w .Xc Move forward .Ar count words. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm x .Xc Delete .Ar count characters at the current cursor position, but no more than there are till the end of the line. .Pp .It Xo .Op Ar buffer .Op Ar count .Cm y .Ar motion .Xc Copy (or .Dq yank ) a text region specified by .Ar count and .Ar motion into a buffer. .Pp .It Xo .Op Ar count1 .Cm z .Op Ar count2 .Cm type .Xc Redraw, optionally repositioning and resizing the screen. If .Ar count2 is specified, limit the screen size to .Ar count2 lines. The following .Cm type characters may be used: .Bl -tag -width Ds .It Cm + If .Ar count1 is specified, place the line .Ar count1 at the top of the screen. Otherwise, display the screen after the current screen. .It Aq Cm carriage-return Place the line .Ar count1 at the top of the screen. .It Cm .\& Place the line .Ar count1 in the center of the screen. .It Cm - Place the line .Ar count1 at the bottom of the screen. .It Cm ^ If .Ar count1 is given, display the screen before the screen before .Ar count1 .Pq i.e. 2 screens before . Otherwise, display the screen before the current screen. .El .Pp .It Xo .Op Ar count .Cm {\& .Xc Move backward .Ar count paragraphs. .Pp .It Xo .Op Ar column .Cm |\& .Xc Move to a specific .Ar column position on the current line. If .Ar column is omitted, move to the start of the current line. .Pp .It Xo .Op Ar count .Cm }\& .Xc Move forward .Ar count paragraphs. .Pp .It Xo .Op Ar count .Cm ~ .Ar motion .Xc If the .Cm tildeop option is not set, reverse the case of the next .Ar count character(s) and no .Ar motion can be specified. Otherwise .Ar motion is mandatory and .Cm ~ reverses the case of the characters in a text region specified by the .Ar count and .Ar motion . .Pp .It Aq Cm interrupt Interrupt the current operation. The .Aq interrupt character is usually .Aq control-C . .El .Sh VI TEXT INPUT COMMANDS The following section describes the commands available in the text input mode of the .Nm vi editor. .Pp .Bl -tag -width Ds -compact .It Aq Cm nul Replay the previous input. .Pp .It Aq Cm control-D Erase to the previous .Ar shiftwidth column boundary. .Pp .It Cm ^ Ns Aq Cm control-D Erase all of the autoindent characters. .Pp .It Cm 0 Ns Aq Cm control-D Erase all of the autoindent characters, and reset the autoindent level. .Pp .It Aq Cm control-T Insert sufficient .Aq tab and .Aq space characters to move forward to the next .Ar shiftwidth column boundary. If the .Cm expandtab option is set, only insert .Aq space characters. .Pp .It Aq Cm erase .It Aq Cm control-H Erase the last character. .Pp .It Aq Cm literal next Escape the next character from any special meaning. The .Aq literal\ \&next character is usually .Aq control-V . .Pp .It Aq Cm escape Resolve all text input into the file, and return to command mode. .Pp .It Aq Cm line erase Erase the current line. .Pp .It Aq Cm control-W .It Aq Cm word erase Erase the last word. The definition of word is dependent on the .Cm altwerase and .Cm ttywerase options. .Pp .Sm off .It Xo .Aq Cm control-X .Bq Cm 0-9A-Fa-f .Cm + .Xc .Sm on Insert a character with the specified hexadecimal value into the text. .Pp .It Aq Cm interrupt Interrupt text input mode, returning to command mode. The .Aq interrupt character is usually .Aq control-C . .El .Sh EX COMMANDS The following section describes the commands available in the .Nm ex editor. In each entry below, the tag line is a usage synopsis for the command. .Pp .Bl -tag -width Ds -compact .It Aq Cm end-of-file Scroll the screen. .Pp .It Cm !\& Ar argument(s) .It Xo .Op Ar range .Cm !\& .Ar argument(s) .Xc Execute a shell command, or filter lines through a shell command. .Pp .It Cm \&" A comment. .Pp .It Xo .Op Ar range .Cm nu Ns Op Cm mber .Op Ar count .Op Ar flags .Xc .It Xo .Op Ar range .Cm # .Op Ar count .Op Ar flags .Xc Display the selected lines, each preceded with its line number. .Pp .It Cm @ Ar buffer .It Cm * Ar buffer Execute a buffer. .Pp .It Xo .Op Ar range .Cm < Ns Op Cm < ... .Op Ar count .Op Ar flags .Xc Shift lines left. .Pp .It Xo .Op Ar line .Cm = .Op Ar flags .Xc Display the line number of .Ar line . If .Ar line is not specified, display the line number of the last line in the file. .Pp .It Xo .Op Ar range .Cm > Ns Op Cm > ... .Op Ar count .Op Ar flags .Xc Shift lines right. .Pp .It Xo .Cm ab Ns Op Cm breviate .Ar lhs rhs .Xc .Nm vi only. Add .Ar lhs as an abbreviation for .Ar rhs to the abbreviation list. .Pp .It Xo .Op Ar line .Cm a Ns Op Cm ppend Ns .Op Cm !\& .Xc The input text is appended after the specified line. .Pp .It Cm ar Ns Op Cm gs Display the argument list. .Pp .It Cm bg .Nm vi only. Background the current screen. .Pp .It Xo .Op Ar range .Cm c Ns Op Cm hange Ns .Op Cm !\& .Op Ar count .Xc The input text replaces the specified range. .Pp .It Xo .Cm chd Ns Op Cm ir Ns .Op Cm !\& .Op Ar directory .Xc .It Xo .Cm cd Ns Op Cm !\& .Op Ar directory .Xc Change the current working directory. .Pp .It Xo .Op Ar range .Cm co Ns Op Cm py .Ar line .Op Ar flags .Xc .It Xo .Op Ar range .Cm t .Ar line .Op Ar flags .Xc Copy the specified lines after the destination .Ar line . .Pp .It Xo .Op Ar range .Cm d Ns Op Cm elete .Op Ar buffer .Op Ar count .Op Ar flags .Xc Delete the lines from the file. .Pp .It Xo .Cm di Ns Op Cm splay .Cm b Ns Oo Cm uffers Oc | .Cm s Ns Oo Cm creens Oc | .Cm t Ns Op Cm ags .Xc Display buffers, screens or tags. .Pp .It Xo .Cm e Ns Op Cm dit Ns | Ns Cm x Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc Edit a different file. The capitalized command opens a new screen below the current screen. .Pp .It Xo .Cm exu Ns Op Cm sage .Op Ar command .Xc Display usage for an .Nm ex command. .Pp .It Xo .Cm f Ns Op Cm ile .Op Ar file .Xc Display and optionally change the file name. .Pp .It Cm fg Op Ar name .Nm vi only. Foreground the specified screen. The capitalized command opens a new screen below the current screen. .Pp .It Xo .Op Ar range .Cm g Ns Op Cm lobal .No / Ns Ar pattern Ns / .Op Ar commands .Xc .It Xo .Op Ar range .Cm v .No / Ns Ar pattern Ns / .Op Ar commands .Xc Apply commands to lines matching .Pq Sq global or not matching .Pq Sq v a pattern. .Pp .It Cm he Ns Op Cm lp Display a help message. .Pp .It Xo .Op Ar line .Cm i Ns Op Cm nsert Ns .Op Cm !\& .Xc The input text is inserted before the specified line. .Pp .It Xo .Op Ar range .Cm j Ns Op Cm oin Ns .Op Cm !\& .Op Ar count .Op Ar flags .Xc Join lines of text together. .Pp .It Xo .Op Ar range .Cm l Ns Op Cm ist .Op Ar count .Op Ar flags .Xc Display the lines unambiguously. .Pp .It Xo .Cm map Ns Op Cm !\& .Op Ar lhs rhs .Xc .Nm vi only. Define or display maps. .Pp .It Xo .Op Ar line .Cm ma Ns Op Cm rk .Aq Ar character .Xc .It Xo .Op Ar line .Cm k Aq Ar character .Xc Mark the line with the mark .Aq Ar character . .Pp .It Xo .Op Ar range .Cm m Ns Op Cm ove .Ar line .Xc Move the specified lines after the target line. .Pp .It Xo .Cm mk Ns Op Cm exrc Ns .Op Cm !\& .Ar file .Xc Write the abbreviations, editor options and maps to the specified .Ar file . .Pp .It Xo .Cm n Ns Op Cm ext Ns .Op Cm !\& .Op Ar file ... .Xc Edit the next file from the argument list. The capitalized command opens a new screen below the current screen. .\" .Pp .\" .It Xo .\" .Op Ar line .\" .Cm o Ns Op Cm pen .\" .No / Ns Ar pattern Ns / .\" .Op Ar flags .\" .Xc .\" Enter open mode. .Pp .It Cm pre Ns Op Cm serve Save the file in a form that can later be recovered using the .Nm ex .Fl r option. .Pp .It Cm prev Ns Oo Cm ious Oc Ns Op Cm !\& Edit the previous file from the argument list. The capitalized command opens a new screen below the current screen. .Pp .It Xo .Op Ar range .Cm p Ns Op Cm rint .Op Ar count .Op Ar flags .Xc Display the specified lines. .Pp .It Xo .Op Ar line .Cm pu Ns Op Cm t .Op Ar buffer .Xc Append buffer contents to the current line. .Pp .It Xo .Cm q Ns Op Cm uit Ns .Op Cm !\& .Xc End the editing session. In split-screen mode, only close the current screen and switch to the previous one. .Pp .It Xo .Op Ar line .Cm r Ns Op Cm ead Ns .Op Cm !\& .Op Ar file .Xc Read a file. .Pp .It Xo .Cm rec Ns Op Cm over .Ar file .Xc Recover .Ar file if it was previously saved. .Pp .It Xo .Cm res Ns Op Cm ize .Op Cm + Ns | Ns Cm - Ns .Ar lines .Xc .Nm vi mode only. Grow or shrink the current screen. .Pp .It Xo .Cm rew Ns Op Cm ind Ns .Op Cm !\& .Xc Rewind the argument list. .Pp .It Xo .Op Ar range .Sm off .Cm s .Oo Cm / Ar pattern Cm / Ar replace Cm / .Op Ar options .Op Ar count .Op Ar flags .Oc .Sm on .Xc .It Xo .Op Ar range .Sm off .Cm & .Op Ar options .Op Ar count .Op Ar flags .Sm on .Xc .It Xo .Op Ar range .Sm off .Cm ~ .Op Ar options .Op Ar count .Op Ar flags .Sm on .Xc Substitute the regular expression .Ar pattern with .Ar replace . When invoked as .Cm & , or if .Cm / Ns Ar pattern Ns Cm / Ns Ar replace Ns Cm / is omitted, .Ar pattern and .Ar replace from the most recent .Cm s command are used. .Cm ~ behaves like .Cm & , except the pattern used is the most recent regular expression used by any command. .Pp The .Ar replace field may contain any of the following sequences: .Bl -tag -width Ds .It Sq & The text matched by .Ar pattern . .It Sq \(a~ The replacement part of the previous .Cm s command. .It Sq % If this is the entire .Ar replace pattern, the replacement part of the previous .Cm s command. .It Sq \e# Where .Sq # is an integer from 1 to 9, the text matched by the #'th subexpression in .Ar pattern . .It Sq \eL Causes the characters up to the end of the line of the next occurrence of .Sq \eE or .Sq \ee to be converted to lowercase. .It Sq \el Causes the next character to be converted to lowercase. .It Sq \eU Causes the characters up to the end of the line of the next occurrence of .Sq \eE or .Sq \ee to be converted to uppercase. .It Sq \eu Causes the next character to be converted to uppercase. .El .Pp The .Ar options field may contain any of the following characters: .Bl -tag -width Ds .It Sq c Prompt for confirmation before each replacement is done. .It Sq g Replace all instances of .Ar pattern in a line, not just the first. .El .Pp .It Xo .Cm se Ns Op Cm t .Sm off .Op option Oo = Oo value Oc Oc \ \&... .Sm on .Pf \ \& Op nooption ... .Op option? ... .Op Ar all .Xc Display or set the editor options described in .Sx SET OPTIONS . .Pp .It Cm sh Ns Op Cm ell Run a shell program. .Pp .It Xo .Cm so Ns Op Cm urce .Ar file .Xc Read and execute .Nm ex commands from a file. .Pp .It Xo .Cm su Ns Op Cm spend Ns .Op Cm !\& .Xc .It Xo .Cm st Ns Op Cm op Ns .Op Cm !\& .Xc .It Aq Cm suspend Suspend the edit session. The .Aq suspend character is usually .Aq control-Z . .Pp .It Xo .Cm ta Ns Op Cm g Ns .Op Cm !\& .Ar tagstring .Xc Edit the file containing the specified tag. The capitalized command opens a new screen below the current screen. .Pp .It Xo .Cm tagn Ns Op Cm ext Ns .Op Cm !\& .Xc Edit the file containing the next context for the current tag. .Pp .It Xo .Cm tagp Ns Op Cm op Ns .Op Cm !\& .Op Ar file | number .Xc Pop to the specified tag in the tags stack. .Pp .It Xo .Cm tagpr Ns Op Cm ev Ns .Op Cm !\& .Xc Edit the file containing the previous context for the current tag. .Pp .It Xo .Cm tagt Ns Op Cm op Ns .Op Cm !\& .Xc Pop to the least recent tag on the tags stack, clearing the stack. .Pp .It Xo .Cm una Ns Op Cm bbreviate .Ar lhs .Xc .Nm vi only. Delete an abbreviation. .Pp .It Cm u Ns Op Cm ndo Undo the last change made to the file. .Pp .It Xo .Cm unm Ns Op Cm ap Ns .Op Cm !\& .Ar lhs .Xc Unmap a mapped string. .Pp .It Cm ve Ns Op Cm rsion Display the version of the .Nm ex Ns / Ns Nm vi editor. .Pp .It Xo .Op Ar line .Cm vi Ns Op Cm sual .Op Ar type .Op Ar count .Op Ar flags .Xc .Nm ex only. Enter .Nm vi . .Pp .It Xo .Cm vi Ns .Op Cm sual Ns .Op Cm !\& .Op Ar +cmd .Op Ar file .Xc .Nm vi mode only. Edit a different file by opening a new screen below the current screen. .Pp .It Xo .Cm viu Ns Op Cm sage .Op Ar command .Xc Display usage for a .Nm vi command. .Pp .It Xo .Op Ar range .Cm w Ns Op Cm rite Ns .Op Cm !\& .Op >> .Op Ar file .Xc Write the file. .Pp .It Xo .Op Ar range .Cm wn Ns Op Cm !\& .Op >> .Op Ar file .Xc Write the file and edit the next file from the argument list. .Pp .It Xo .Op Ar range .Cm wq Ns Op Cm !\& .Op >> .Op Ar file .Xc Write the file and exit the editor. In split-screen mode, close the current screen and switch to the previous one. .Pp .It Xo .Op Ar range .Cm x Ns Op Cm it Ns .Op Cm !\& .Op Ar file .Xc Exit the editor, writing the file if it has been modified. In split-screen mode, close the current screen and switch to the previous one. .Pp .It Xo .Op Ar range .Cm ya Ns Op Cm nk .Op Ar buffer .Op Ar count .Xc Copy the specified lines to a buffer. .Pp .It Xo .Op Ar line .Cm z .Op Ar type .Op Ar count .Op Ar flags .Xc Adjust the window. .El .Pp For .Cm e , .Cm fg , .Cm n , .Cm prev , .Cm ta , and .Cm vi , if the first letter of the command is capitalized, the current screen is split and the new file is displayed in addition to the current screen. This feature is only available in .Nm vi , not in .Nm ex . .Sh SET OPTIONS There are a large number of options that can change the editor's behavior, using the .Cm set command. This section describes the options, their abbreviations and their default values. .Pp In each entry below, the first part of the tag line is the full name of the option, followed by any equivalent abbreviations. The part in square brackets is the default value of the option. Most of the options are boolean, i.e. they are either on or off, and do not have an associated value. .Pp Options apply to both .Nm ex and .Nm vi modes, unless otherwise specified. .Bl -tag -width Ds .It Cm altnotation , an Bq off Display most control characters less than 0x20 using notation. Carriage feed, escape, and delete are displayed as , , and , respectively. .It Cm altwerase Bq off .Nm vi only. Select an alternate word erase algorithm. .It Cm autoindent , ai Bq off Automatically indent new lines. .It Cm autoprint , ap Bq on .Nm ex only. Display the current line automatically. .It Cm autowrite , aw Bq off Write modified files automatically when changing files or suspending the editor session. .It Cm backup Bq \&"\&" Back up files before they are overwritten. .It Cm beautify , bf Bq off Discard control characters. .It Cm bserase , bse Bq off .Nm vi only. Immediately erase backspaced characters from the screen. .It Cm cdpath Bq "environment variable CDPATH, or current directory" The directory paths used as path prefixes for the .Cm cd command. .It Cm cedit Bq no default Set the character to edit the colon command-line history. .It Cm columns , co Bq 80 Set the number of columns in the screen. .It Cm comment Bq off .Nm vi only. Skip leading comments in shell, C and C++ language files. .It Cm edcompatible , ed Bq off Remember the values of the .Sq c and .Sq g suffixes to the .Cm s , & and .Cm ~ commands, instead of initializing them as unset for each new command. .It Cm escapetime Bq 2 The tenths of a second .Nm ex Ns / Ns Nm vi waits for a subsequent key to complete an .Aq escape key mapping. .It Cm errorbells , eb Bq off .Nm ex only. Announce error messages with a bell. .It Cm expandtab , et Bq off Expand .Aq tab characters to .Aq space when inserting, replacing or shifting text, autoindenting, indenting with .Aq Ic control-T , outdenting with .Aq Ic control-D , or when filtering lines with the .Cm !\& command. .It Cm exrc , ex Bq off Read the startup files in the local directory. .It Cm extended Bq off Use extended regular expressions .Pq EREs rather than basic regular expressions .Pq BREs . See .Xr re_format 7 for more information on regular expressions. .It Cm filec Bq Aq tab Set the character to perform file path completion on the colon command line. .It Cm flash Bq off Flash the screen instead of beeping the keyboard on error. .It Cm hardtabs , ht Bq 0 Set the spacing between hardware tab settings. This option currently has no effect. .It Cm iclower Bq off Makes all regular expressions case-insensitive, as long as an upper-case letter does not appear in the search string. .It Cm ignorecase , ic Bq off Ignore case differences in regular expressions. .It Cm imctrl Bq off Control input method using escape sequences compatible with Tera Term and RLogin. The state of the input method commands specified by imkey option is saved and restored automatically. This input method is deactivated upon returning to command mode. If the terminal in use does not accept these escape sequences, the screen display may be corrupted. .It Cm imkey [/?aioAIO] Set commands which the state of input method is restored and saved on entering and leaving, respectively. .It Cm keytime Bq 6 The tenths of a second .Nm ex Ns / Ns Nm vi waits for a subsequent key to complete a key mapping. .It Cm leftright Bq off .Nm vi only. Do left-right scrolling. .It Cm lines , li Bq 24 .Nm vi only. Set the number of lines in the screen. .It Cm list Bq off Display lines in an unambiguous fashion. .It Cm lock Bq on Attempt to get an exclusive lock on any file being edited, read or written. .It Cm magic Bq on When turned off, all regular expression characters except for .Sq ^ and .Sq $ are treated as ordinary characters. Preceding individual characters by .Sq \e re-enables them. .It Cm matchtime Bq 7 .Nm vi only. The tenths of a second .Nm ex Ns / Ns Nm vi pauses on the matching character when the .Cm showmatch option is set. .It Cm mesg Bq on Permit messages from other users. .It Cm noprint Bq \&"\&" Characters that are never handled as printable characters. .It Cm number , nu Bq off Precede each line displayed with its current line number. .It Cm octal Bq off Display unknown characters as octal numbers, instead of the default hexadecimal. .It Cm open Bq on .Nm ex only. If this option is not set, the .Cm open and .Cm visual commands are disallowed. .It Cm paragraphs , para Bq "IPLPPPQPP LIpplpipbpBlBdPpLpIt" .Nm vi only. Define additional paragraph boundaries for the .Cm {\& and .Cm }\& commands. .It Cm path Bq \&"\&" Define additional directories to search for files being edited. .It Cm print Bq \&"\&" Characters that are always handled as printable characters. .It Cm prompt Bq on .Nm ex only. Display a command prompt. .It Cm readonly , ro Bq off Mark the file and session as read-only. .It Cm recdir Bq /var/tmp/vi.recover The directory where recovery files are stored. .It Cm remap Bq on Remap keys until resolved. .It Cm report Bq 5 Set the number of lines about which the editor reports changes or yanks. .It Cm ruler Bq off .Nm vi only. Display a row/column/percentage ruler on the colon command line. .It Cm scroll , scr Bq "($LINES \- 1) / 2" Set the number of lines scrolled. .It Cm searchincr Bq off Makes the .Cm / and .Cm ?\& commands incremental. .It Cm sections , sect Bq "NHSHH HUnhshShSs" .Nm vi only. Define additional section boundaries for the .Cm [[ and .Cm ]] commands. .It Cm secure Turns off all access to external programs. Once set, this option can't be disabled. .It Cm shell , sh Bq "environment variable SHELL, or /bin/sh" Select the shell used by the editor. .It Cm shellmeta Bq ~{[*?$`'\&"\e Set the meta characters checked to determine if file name expansion is necessary. .It Cm shiftwidth , sw Bq 8 Set the autoindent and shift command indentation width. .It Cm showmatch , sm Bq off .Nm vi only. Note matching .Sq { and .Sq \&( for .Sq } and .Sq )\& characters. .It Cm showfilename Bq off .Nm vi only. Display the file name on the colon command line. .It Cm showmode , smd Bq off .Nm vi only. Display the current editor mode and a .Dq modified flag. .It Cm sidescroll Bq 16 .Nm vi only. Set the amount a left-right scroll will shift. .It Cm tabstop , ts Bq 8 This option sets tab widths for the editor display. .It Cm taglength , tl Bq 0 Set the number of significant characters in tag names. .It Cm tags , tag Bq tags Set the list of tags files. .It Xo .Cm term , ttytype , tty .Bq "environment variable TERM" .Xc Set the terminal type. .It Cm terse Bq off This option has historically made editor messages less verbose. It has no effect in this implementation. .It Cm tildeop Bq off Modify the .Cm ~ command to take an associated motion. .It Cm timeout , to Bq on Time out on keys which may be mapped. .It Cm ttywerase Bq off .Nm vi only. Select an alternate erase algorithm. .It Cm verbose Bq off .Nm vi only. Display an error message for every error. .It Cm visibletab , vt Bq off .Nm vi only. Displays tabs visibly while editing. .It Cm w300 Bq no default .Nm vi only. Set the window size if the baud rate is less than 1200 baud. .It Cm w1200 Bq no default .Nm vi only. Set the window size if the baud rate is equal to 1200 baud. .It Cm w9600 Bq no default .Nm vi only. Set the window size if the baud rate is greater than 1200 baud. .It Cm warn Bq on .Nm ex only. This option causes a warning message to be printed on the terminal if the file has been modified since it was last written, before a .Cm !\& command. .It Xo .Cm window , w , wi .Bq "environment variable LINES \- 1" .Xc Set the window size for the screen. Note that POSIX 1003.2 requires honoring the environment variable value, even if the terminal has been resized. .It Cm windowname Bq off Change the icon/window name to the current file name even if it can't be restored on editor exit. .It Cm wraplen , wl Bq 0 .Nm vi only. Break lines automatically, the specified number of columns from the left-hand margin. If both the .Cm wraplen and .Cm wrapmargin edit options are set, the .Cm wrapmargin value is used. .It Cm wrapmargin , wm Bq 0 .Nm vi only. Break lines automatically, the specified number of columns from the right-hand margin. If both the .Cm wraplen and .Cm wrapmargin edit options are set, the .Cm wrapmargin value is used. .It Cm wrapscan , ws Bq on Set searches to wrap around the end or beginning of the file. .It Cm writeany , wa Bq off Turn off file-overwriting checks. .El .Sh ENVIRONMENT .Bl -tag -width "COLUMNS" .It Ev COLUMNS The number of columns on the screen. This value overrides any system or terminal specific values, as POSIX 1003.2 requires honoring this environment variable value, even if the terminal is later resized. If the .Ev COLUMNS environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm columns option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .It Ev EXINIT A list of .Nm ex startup commands, read after .Pa /etc/vi.exrc unless the variable .Ev NEXINIT is also set. .It Ev HOME The user's home directory, used as the initial directory path for the startup .Pa $HOME/.nexrc and .Pa $HOME/.exrc files. This value is also used as the default directory for the .Nm vi .Cm cd command. .It Ev LINES The number of rows on the screen. This value overrides any system or terminal specific values as POSIX 1003.2 requires honoring the environment variable value, even if the terminal is later resized. If the .Ev LINES environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm lines option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .It Ev NEXINIT A list of .Nm ex startup commands, read after .Pa /etc/vi.exrc . .It Ev SHELL The user's shell of choice (see also the .Cm shell option). .It Ev TERM The user's terminal type. The default is the type .Dq vt100 . If the .Ev TERM environment variable is not set when .Nm ex Ns / Ns Nm vi runs, or the .Cm term option is explicitly reset by the user, .Nm ex Ns / Ns Nm vi enters the value into the environment. .El .Sh ASYNCHRONOUS EVENTS .Bl -tag -width "SIGWINCH" -compact .It Dv SIGALRM .Nm vi Ns / Ns Nm ex uses this signal for periodic backups of file modifications and to display .Dq busy messages when operations are likely to take a long time. .Pp .It Dv SIGHUP .It Dv SIGTERM If the current buffer has changed since it was last written in its entirety, the editor attempts to save the modified file so it can be later recovered. See the .Nm vi Ns / Ns Nm ex reference manual section .Sx Recovery for more information. .Pp .It Dv SIGINT .It Dv SIGQUIT When an interrupt occurs, the current operation is halted and the editor returns to the command level. If interrupted during text input, the text already input is resolved into the file as if the text input had been normally terminated. .Pp .It Dv SIGWINCH The screen is resized. See the .Nm vi Ns / Ns Nm ex reference manual section .Sx Sizing the Screen for more information. .\" .Pp .\" .It Dv SIGCONT .\" .It Dv SIGTSTP .\" .Nm vi Ns / Ns Nm ex .\" ignores these signals. .El .Sh FILES .Bl -tag -width "/var/tmp/vi.recover" .It Pa /bin/sh The default user shell. .It Pa /etc/vi.exrc System-wide .Nm vi startup file. It is read for .Nm ex commands first in the startup sequence. Must be owned by root or the user, and writable only by the owner. .It Pa /tmp Temporary file directory. .It Pa /var/tmp/vi.recover The default recovery file directory. .It Pa $HOME/.nexrc First choice for user's home directory startup file, read for .Nm ex commands right after .Pa /etc/vi.exrc unless either .Ev NEXINIT or .Ev EXINIT are set. Must be owned by root or the user, and writable only by the owner. .It Pa $HOME/.exrc Second choice for user's home directory startup file, read for .Nm ex commands under the same conditions as .Pa $HOME/.nexrc . .It Pa .nexrc First choice for local directory startup file, read for .Nm ex commands at the end of the startup sequence if the .Cm exrc option was turned on earlier. Must be owned by the user and writable only by the owner. .It Pa .exrc Second choice for local directory startup file, read for .Nm ex commands under the same conditions as .Pa .nexrc . .El .Sh EXIT STATUS The .Nm ex and .Nm vi utilities exit 0 on success, and >0 if an error occurs. .Sh SEE ALSO .Xr ctags 1 , .Xr re_format 7 .Rs .Sh STANDARDS .Nm nex Ns / Ns Nm nvi is close to .St -p1003.1-2008 . It deviates in the following respects: .Bl -bullet .It The .Ic s .Nm ex command may not be called as .Ic substitute . .It The .Ic directory , redraw and .Ic slowopen settings are not implemented. .It The .Ic paragraphs and .Ic sections settings default to values useful for editing .Xr mdoc 7 manuals. .It The .Ev TMPDIR environment variable is ignored. .It In insert mode, entering .Aq Ic control-H , .Aq Ic erase , or .Aq Ic kill following a backslash will not embed the control character in the text. .El .Sh HISTORY The .Nm ex editor first appeared in .Bx 1 . The .Nm nex Ns / Ns Nm nvi replacements for the .Nm ex Ns / Ns Nm vi editor first appeared in .Bx 4.4 . .Sh AUTHORS .An Bill Joy wrote the original version of .Nm ex in 1977. ================================================ FILE: docs/USD.doc/vitut/vi.apwh.ms ================================================ .\" $OpenBSD: vi.apwh.ms,v 1.5 2004/01/24 12:29:13 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)vi.apwh.ms 8.2 (Berkeley) 8/18/96 .\" .if n \{\ .po 5n .ll 70n .\} .nr LL 6.5i .nr FL 6.5i .TL Vi Command & Function Reference .AU CB 2675 Alan P.W. Hewett .sp Revised for version 2.12 by Mark Horton .\" .CB .NH 1 Author's Disclaimer .LP This document does not claim to be 100% complete. There are a few commands listed in the original document that I was unable to test either because I do not speak \fBlisp\fR, because they required programs we don't have, or because I wasn't able to make them work. In these cases I left the command out. The commands listed in this document have been tried and are known to work. It is expected that prospective users of this document will read it once to get the flavor of everything that \fBvi\fR can do and then use it as a reference document. Experimentation is recommended. If you don't understand a command, try it and see what happens. .LP [Note: In revising this document, I have attempted to make it completely reflect version 2.12 of .B vi . It does not attempt to document the VAX version (version 3), but with one or two exceptions (wrapmargin, arrow keys) everything said about 2.12 should apply to 3.1. .I "Mark Horton" ] .NH 1 Notation .LP \fB[option]\fR is used to denote optional parts of a command. Many \fBvi\fR commands have an optional count. \fB[cnt]\fR means that an optional number may precede the command to multiply or iterate the command. \fB{variable item}\fR is used to denote parts of the command which must appear, but can take a number of different values. \fB\fR means that the character or one of the characters in the range described between the two angle brackets is to be typed. For example \fB\fR means the \fBescape\fR key is to be typed. \fB\fR means that a lower case letter is to be typed. \fB^\fR means that the character is to be typed as a \fBcontrol\fR character, that is, with the \fB\fR key held down while simultaneously typing the specified character. In this document control characters will be denoted using the \fIupper case\fR character, but ^ and ^ are equivalent. That is, for example, \fB<^D>\fR is equal to \fB<^d>\fR. The most common character abbreviations used in this list are as follows: .RS .IP 8 escape, octal 033 .IP 8 carriage return, ^M, octal 015 .IP 8 linefeed ^J, octal 012 .IP 8 newline, ^J, octal 012 (same as linefeed) .IP 8 backspace, ^H, octal 010 .IP 8 tab, ^I, octal 011 .IP 8 bell, ^G, octal 07 .IP 8 formfeed, ^L, octal 014 .IP 8 space, octal 040 .IP 8 delete, octal 0177 .RE .sp 1 .NH 1 Basics .LP To run \fBvi\fR the shell variable \fBTERM\fR must be defined and exported to your environment. How you do this depends on which shell you are using. You can tell which shell you have by the character it prompts you for commands with. The Bourne shell prompts with `$', and the C shell prompts with `%'. For these examples, we will suppose that you are using an HP 2621 terminal, whose termcap name is ``2621''. .NH 2 Bourne Shell .LP To manually set your terminal type to 2621 you would type: .DS export TERM=2621 .DE .PP There are various ways of having this automatically or semi-automatically done when you log in. Suppose you usually dial in on a 2621. You want to tell this to the machine, but still have it work when you use a hardwired terminal. The recommended way, if you have the .B tset program, is to use the sequence .DS tset \-s \-d 2621 > tset$$ \&. tset$$ rm tset$$ .DE in your .login (for csh) or the same thing using `.' instead of `source' in your .profile (for sh). The above line says that if you are dialing in you are on a 2621, but if you are on a hardwired terminal it figures out your terminal type from an on-line list. .NH 2 The C Shell .LP To manually set your terminal type to 2621 you would type: .DS setenv TERM 2621 .DE .PP There are various ways of having this automatically or semi-automatically done when you log in. Suppose you usually dial in on a 2621. You want to tell this to the machine, but still have it work when you use a hardwired terminal. The recommended way, if you have the .B tset program, is to use the sequence .DS tset \-s \-d 2621 > tset$$ source tset$$ rm tset$$ .DE in your .login.* .FS * On a version 6 system without environments, the invocation of tset is simpler, just add the line ``tset \-d 2621'' to your .login or .profile. .FE The above line says that if you are dialing in you are on a 2621, but if you are on a hardwired terminal it figures out your terminal type from an on-line list. .NH 1 Normal Commands .LP \fBVi\fR is a visual editor with a window on the file. What you see on the screen is \fBvi\fR's current notion of what your file will contain, (at this point in the file), when it is written out. Most commands do not cause any change in the screen until the complete command is typed. Should you get confused while typing a command, you can abort the command by typing an character. You will know you are back to command level when you hear a . Usually typing an will produce the same result. When \fBvi\fR gets an improperly formatted command it rings the . Following are the \fBvi\fR commands broken down by function. .NH 2 Entry and Exit .LP To enter .B vi on a particular .I file , type .DS \fBvi\fP \fIfile\fP .DE The file will be read in and the cursor will be placed at the beginning of the first line. The first screenfull of the file will be displayed on the terminal. .PP To get out of the editor, type .DS ZZ .DE If you are in some special mode, such as input mode or the middle of a multi-keystroke command, it may be necessary to type first. .NH 2 Cursor and Page Motion .LP .RS .B NOTE: The arrow keys (see the next four commands) on certain kinds of terminals will not work with the PDP-11 version of vi. The control versions or the hjkl versions will work on any terminal. Experienced users prefer the hjkl keys because they are always right under their fingers. Beginners often prefer the arrow keys, since they do not require memorization of which hjkl key is which. The mnemonic value of hjkl is clear from looking at the keyboard of an adm3a. .sp .IP "[cnt]h or [cnt]^H" 16 .br Move the cursor to the left one character. Cursor stops at the left margin of the page. If cnt is given, these commands move that many spaces. .IP "[cnt]^N or [cnt]j or [cnt]^J" 16 .br Move down one line. Moving off the screen scrolls the window to force a new line onto the screen. Mnemonic: \fBN\fRext .IP "[cnt]^P or [cnt]k" 16 .br Move up one line. Moving off the top of the screen forces new text onto the screen. Mnemonic: \fBP\fRrevious .IP "[cnt] or [cnt]l" 16 .br Move to the right one character. Cursor will not go beyond the end of the line. .IP [cnt]- 16 Move the cursor up the screen to the beginning of the next line. Scroll if necessary. .IP "[cnt]+ or [cnt]" 16 .sp 1 Move the cursor down the screen to the beginning of the next line. Scroll up if necessary. .IP "[cnt]$" 16 Move the cursor to the end of the line. If there is a count, move to the end of the line "cnt" lines forward in the file. .IP "^" 16 Move the cursor to the beginning of the first word on the line. .IP "0" 16 Move the cursor to the left margin of the current line. .IP "[cnt]|" 16 Move the cursor to the column specified by the count. The default is column zero. .IP "[cnt]w" 16 Move the cursor to the beginning of the next word. If there is a count, then move forward that many words and position the cursor at the beginning of the word. Mnemonic: next-\fBw\fRord .IP "[cnt]W" 16 Move the cursor to the beginning of the next word which follows a "whitespace" (,, or ). Ignore other punctuation. .IP "[cnt]b" 16 Move the cursor to the preceding word. Mnemonic: \fBb\fRackup-word .IP "[cnt]B" 16 Move the cursor to the preceding word that is separated from the current word by a "whitespace" (,, or ). .IP "[cnt]e" 16 Move the cursor to the end of the current word or the end of the "cnt"'th word hence. Mnemonic: \fBe\fRnd-of-word .IP "[cnt]E" 16 Move the cursor to the end of the current word which is delimited by "whitespace" (,, or ). .IP "[line number]G" 16 .br Move the cursor to the line specified. Of particular use are the sequences "1G" and "G", which move the cursor to the beginning and the end of the file respectively. Mnemonic: \fBG\fRo-to .LP .B NOTE: The next four commands (^D, ^U, ^F, ^B) are not true motion commands, in that they cannot be used as the object of commands such as delete or change. .IP "[cnt]^D" 16 Move the cursor down in the file by "cnt" lines (or the last "cnt" if a new count isn't given. The initial default is half a page.) The screen is simultaneously scrolled up. Mnemonic: \fBD\fRown .IP "[cnt]^U" 16 Move the cursor up in the file by "cnt" lines. The screen is simultaneously scrolled down. Mnemonic: \fBU\fRp .IP "[cnt]^F" 16 Move the cursor to the next page. A count moves that many pages. Two lines of the previous page are kept on the screen for continuity if possible. Mnemonic: \fBF\fRorward-a-page .IP "[cnt]^B" 16 Move the cursor to the previous page. Two lines of the current page are kept if possible. Mnemonic: \fBB\fRackup-a-page .IP "[cnt](" 16 Move the cursor to the beginning of the next sentence. A sentence is defined as ending with a ".", "!", or "?" followed by two spaces or a . .IP "[cnt])" 16 Move the cursor backwards to the beginning of a sentence. .IP "[cnt]}" 16 Move the cursor to the beginning of the next paragraph. This command works best inside \fBnroff\fR documents. It understands two sets of \fBnroff\fR macros, \fB\-ms\fR and \fB\-mm\fR, for which the commands ".IP", ".LP", ".PP", ".QP", "P", as well as the nroff command ".bp" are considered to be paragraph delimiters. A blank line also delimits a paragraph. The \fBnroff\fR macros that it accepts as paragraph delimiters is adjustable. See \fBparagraphs\fR under the \fBSet Commands\fR section. .IP "[cnt]{" 16 Move the cursor backwards to the beginning of a paragraph. .IP "]]" 16 Move the cursor to the next "section", where a section is defined by two sets of \fBnroff\fR macros, \fB\-ms\fR and \fB\-mm\fR, in which ".NH", ".SH", and ".H" delimit a section. A line beginning with a sequence, or a line beginning with a "{" are also considered to be section delimiters. The last option makes it useful for finding the beginnings of C functions. The \fBnroff\fR macros that are used for section delimiters can be adjusted. See \fBsections\fR under the \fBSet Commands\fR section. .IP "[[" 16 Move the cursor backwards to the beginning of a section. .IP "%" 16 Move the cursor to the matching parenthesis or brace. This is very useful in C or lisp code. If the cursor is sitting on a \fB( ) {\fR or \fB}\fR the cursor is moved to the matching character at the other end of the section. If the cursor is not sitting on a brace or a parenthesis, \fBvi\fR searches forward until it finds one and then jumps to the match mate. .IP "[cnt]H" 16 If there is no count move the cursor to the top left position on the screen. If there is a count, then move the cursor to the beginning of the line "cnt" lines from the top of the screen. Mnemonic: \fBH\fRome .IP "[cnt]L" 16 If there is no count move the cursor to the beginning of the last line on the screen. If there is a count, then move the cursor to the beginning of the line "cnt" lines from the bottom of the screen. Mnemonic: \fBL\fRast .IP "M" 16 Move the cursor to the beginning of the middle line on the screen. Mnemonic: \fBM\fRiddle .IP "m" 16 This command does not move the cursor, but it \fBmarks\fR the place in the file and the character "" becomes the label for referring to this location in the file. See the next two commands. Mnemonic: \fBm\fRark .B NOTE: The mark command is not a motion, and cannot be used as the target of commands such as delete. .IP "\(aa" 16 Move the cursor to the beginning of the line that is marked with the label "". .IP "\(ga" 16 Move the cursor to the exact position on the line that was marked with with the label "". .IP "\(aa\(aa" 16 Move the cursor back to the beginning of the line where it was before the last "non-relative" move. A "non-relative" move is something such as a search or a jump to a specific line in the file, rather than moving the cursor or scrolling the screen. .IP "\(ga\(ga" 16 Move the cursor back to the exact spot on the line where it was located before the last "non-relative" move. .RE .NH 2 Searches .LP The following commands allow you to search for items in a file. .RS .IP [cnt]f{chr} 16 .sp 1 Search forward on the line for the next or "cnt"'th occurrence of the character "chr". The cursor is placed \fBat\fR the character of interest. Mnemonic: \fBf\fRind character .IP [cnt]F{chr} 16 .sp 1 Search backwards on the line for the next or "cnt"'th occurrence of the character "chr". The cursor is placed \fBat\fR the character of interest. .IP [cnt]t{chr} 16 .sp 1 Search forward on the line for the next or "cnt"'th occurrence of the character "chr". The cursor is placed \fBjust preceding\fR the character of interest. Mnemonic: move cursor up \fBt\fRo character .IP [cnt]T{chr} 16 .sp 1 Search backwards on the line for the next or "cnt"'th occurrence of the character "chr". The cursor is placed \fBjust preceding\fR the character of interest. .IP "[cnt];" 16 Repeat the last "f", "F", "t" or "T" command. .IP "[cnt]," 16 Repeat the last "f", "F", "t" or "T" command, but in the opposite search direction. This is useful if you overshoot. .IP "[cnt]/[string]/" 16 .br Search forward for the next occurrence of "string". Wrap around at the end of the file does occur. The final \fB\fR is not required. .IP "[cnt]?[string]?" 16 .br Search backwards for the next occurrence of "string". If a count is specified, the count becomes the new window size. Wrap around at the beginning of the file does occur. The final \fB\fR is not required. .IP n 16 Repeat the last /[string]/ or ?[string]? search. Mnemonic: \fBn\fRext occurrence. .IP N 16 Repeat the last /[string]/ or ?[string]? search, but in the reverse direction. .IP ":g/[string]/[editor command]" 16 .sp 1 Using the \fB:\fR syntax it is possible to do global searches ala the standard UNIX "ed" editor. .RE .NH 2 Text Insertion .LP The following commands allow for the insertion of text. All multicharacter text insertions are terminated with an character. The last change can always be \fBundone\fR by typing a \fBu\fR. The text insert in insertion mode can contain newlines. .RS .IP a{text} 16 Insert text immediately following the cursor position. Mnemonic: \fBa\fRppend .IP A{text} 16 Insert text at the end of the current line. Mnemonic: \fBA\fRppend .IP i{text} 16 Insert text immediately preceding the cursor position. Mnemonic: \fBi\fRnsert .IP I{text} 16 Insert text at the beginning of the current line. .IP o{text} 16 Insert a new line after the line on which the cursor appears and insert text there. Mnemonic: \fBo\fRpen new line .IP O{text} 16 Insert a new line preceding the line on which the cursor appears and insert text there. .RE .NH 2 Text Deletion .LP The following commands allow the user to delete text in various ways. All changes can always be \fBundone\fR by typing the \fBu\fR command. .RS .IP "[cnt]x" 16 Delete the character or characters starting at the cursor position. .IP "[cnt]X" 16 Delete the character or characters starting at the character preceding the cursor position. .IP "D" 16 Deletes the remainder of the line starting at the cursor. Mnemonic: \fBD\fRelete the rest of line .IP "[cnt]d{motion}" 16 .br Deletes one or more occurrences of the specified motion. Any motion from sections 4.1 and 4.2 can be used here. The d can be stuttered (e.g. [cnt]dd) to delete cnt lines. .RE .NH 2 Text Replacement .LP The following commands allow the user to simultaneously delete and insert new text. All such actions can be \fBundone\fR by typing \fBu\fR following the command. .RS .IP "r" 16 Replaces the character at the current cursor position with . This is a one character replacement. No is required for termination. Mnemonic: \fBr\fReplace character .IP "R{text}" 16 Starts overlaying the characters on the screen with whatever you type. It does not stop until an is typed. .IP "[cnt]s{text}" 16 Substitute for "cnt" characters beginning at the current cursor position. A "$" will appear at the position in the text where the "cnt"'th character appears so you will know how much you are erasing. Mnemonic: \fBs\fRubstitute .IP "[cnt]S{text}" 16 Substitute for the entire current line (or lines). If no count is given, a "$" appears at the end of the current line. If a count of more than 1 is given, all the lines to be replaced are deleted before the insertion begins. .IP "[cnt]c{motion}{text}" 16 .br Change the specified "motion" by replacing it with the insertion text. A "$" will appear at the end of the last item that is being deleted unless the deletion involves whole lines. Motion's can be any motion from sections 4.1 or 4.2. Stuttering the c (e.g. [cnt]cc) changes cnt lines. .RE .NH 2 Moving Text .LP \fBVi\fR provides a number of ways of moving chunks of text around. There are nine buffers into which each piece of text which is deleted or "yanked" is put in addition to the "undo" buffer. The most recent deletion or yank is in the "undo" buffer and also usually in buffer 1, the next most recent in buffer 2, and so forth. Each new deletion pushes down all the older deletions. Deletions older than 9 disappear. There is also a set of named registers, a-z, into which text can optionally be placed. If any delete or replacement type command is preceded by \fB"\fR, that named buffer will contain the text deleted after the command is executed. For example, \fB"a3dd\fR will delete three lines starting at the current line and put them in buffer \fB"a\fR.* .FS * Referring to an upper case letter as a buffer name (A-Z) is the same as referring to the lower case letter, except that text placed in such a buffer is appended to it instead of replacing it. .FE There are two more basic commands and some variations useful in getting and putting text into a file. .RS .IP ["][cnt]y{motion} 16 .sp 1 Yank the specified item or "cnt" items and put in the "undo" buffer or the specified buffer. The variety of "items" that can be yanked is the same as those that can be deleted with the "d" command or changed with the "c" command. In the same way that "dd" means delete the current line and "cc" means replace the current line, "yy" means yank the current line. .IP ["][cnt]Y 16 Yank the current line or the "cnt" lines starting from the current line. If no buffer is specified, they will go into the "undo" buffer, like any delete would. It is equivalent to "yy". Mnemonic: \fBY\fRank .IP ["]p 16 Put "undo" buffer or the specified buffer down \fBafter\fR the cursor. If whole lines were yanked or deleted into the buffer, then they will be put down on the line following the line the cursor is on. If something else was deleted, like a word or sentence, then it will be inserted immediately following the cursor. Mnemonic: \fBp\fRut buffer .IP It should be noted that text in the named buffers remains there when you start editing a new file with the \fB:e file\fR command. Since this is so, it is possible to copy or delete text from one file and carry it over to another file in the buffers. However, the undo buffer and the ability to undo are lost when changing files. .IP ["]P 16 Put "undo" buffer or the specified buffer down \fBbefore\fR the cursor. If whole lines where yanked or deleted into the buffer, then they will be put down on the line preceding the line the cursor is on. If something else was deleted, like a word or sentence, then it will be inserted immediately preceding the cursor. .IP [cnt]>{motion} 16 The shift operator will right shift all the text from the line on which the cursor is located to the line where the \fBmotion\fR is located. The text is shifted by one \fBshiftwidth\fR. (See section 6.) \fB>>\fR means right shift the current line or lines. .IP [cnt]<{motion} 16 The shift operator will left shift all the text from the line on which the cursor is located to the line where the \fBitem\fR is located. The text is shifted by one \fBshiftwidth\fR. (See section 6.) \fB<<\fR means left shift the current line or lines. Once the line has reached the left margin it is not further affected. .IP [cnt]={motion} 16 .\" Prettyprints the indicated area according to .\" .B lisp .\" conventions. .\" The area should be a lisp s-expression. Displays the line number. If no prefix is specified,the line number of the last line in the file is displayed. .RE .NH 2 Miscellaneous Commands .LP \fBVi\fR has a number of miscellaneous commands that are very useful. They are: .RS .IP ZZ 16 This is the normal way to exit from vi. If any changes have been made, the file is written out. Then you are returned to the shell. .IP ^L 16 Redraw the current screen. This is useful if someone "write"s you while you are in "vi" or if for any reason garbage gets onto the screen. .IP ^R 16 On dumb terminals, those not having the "delete line" function (the vt100 is such a terminal), \fBvi\fR saves redrawing the screen when you delete a line by just marking the line with an "@" at the beginning and blanking the line. If you want to actually get rid of the lines marked with "@" and see what the page looks like, typing a ^R will do this. .IP \s+4.\s0 16 "Dot" is a particularly useful command. It repeats the last text modifying command. Therefore you can type a command once and then to another place and repeat it by just typing ".". .IP u 16 Perhaps the most important command in the editor, u undoes the last command that changed the buffer. Mnemonic: \fBu\fRndo .IP U 16 Undo all the text modifying commands performed on the current line since the last time you moved onto it. .IP [cnt]J 16 Join the current line and the following line. The is deleted and the two lines joined, usually with a space between the end of the first line and the beginning of what was the second line. If the first line ended with a "period", then two spaces are inserted. A count joins the next cnt lines. Mnemonic: \fBJ\fRoin lines .IP Q 16 Switch to \fBex\fR editing mode. In this mode \fBvi\fR will behave very much like \fBed\fR. The editor in this mode will operate on single lines normally and will not attempt to keep the "window" up to date. .\" Once in this mode it is also possible to switch to the \fBopen\fR .\" mode of editing. By entering the command \fB[line number]open\fR .\" you enter this mode. It is similar to the normal visual mode .\" except the window is only \fBone\fR line long. Mnemonic: \fBQ\fRuit visual mode .IP ^] 16 An abbreviation for a tag command. The cursor should be positioned at the beginning of a word. That word is taken as a tag name, and the tag with that name is found as if it had been typed in a :tag command. .IP [cnt]!{motion}{UNIX\ cmd} 16 .br Any UNIX filter (e.g. command that reads the standard input and outputs something to the standard output) can be sent a section of the current file and have the output of the command replace the original text. Useful examples are programs like \fBsort\fR and \fBnroff\fR. For instance, using \fBsort\fR it would be possible to sort a section of the current file into a new list. Using \fB!!\fR means take a line or lines starting at the line the cursor is currently on and pass them to the UNIX command. .B NOTE: To just escape to the shell for one command, use :!{cmd}, see section 5. .IP z{cnt} 16 This resets the current window size to "cnt" lines and redraws the screen. .RE .NH 2 Special Insert Characters .LP There are some characters that have special meanings during insert modes. They are: .RS .IP ^V 16 During inserts, typing a ^V allows you to quote control characters into the file. Any character typed after the ^V will be inserted into the file. .IP [^]^D\ or\ [0]^D 16 <^D> without any argument backs up one \fBshiftwidth\fR. This is necessary to remove indentation that was inserted by the \fBautoindent\fR feature. ^<^D> temporarily removes all the autoindentation, thus placing the cursor at the left margin. On the next line, the previous indent level will be restored. This is useful for putting "labels" at the left margin. 0<^D> says remove all autoindents and stay that way. Thus the cursor moves to the left margin and stays there on successive lines until 's are typed. As with the , the <^D> is only effective before any other "non-autoindent" controlling characters are typed. Mnemonic: \fBD\fRelete a shiftwidth .IP ^W 16 If the cursor is sitting on a word, <^W> moves the cursor back to the beginning of the word, thus erasing the word from the insert. Mnemonic: erase \fBW\fRord .IP 16 The backspace always serves as an erase during insert modes in addition to your normal "erase" character. To insert a into your file, use the <^V> to quote it. .RE .NH 1 \fB:\fR Commands .LP Typing a ":" during command mode causes \fBvi\fR to put the cursor at the bottom on the screen in preparation for a command. In the ":" mode, \fBvi\fR can be given most \fBed\fR commands. It is also from this mode that you exit from \fBvi\fR or switch to different files. All commands of this variety are terminated by a , , or . .RS .IP ":w[!] [file]" 16 Causes \fBvi\fR to write out the current text to the disk. It is written to the file you are editing unless "file" is supplied. If "file" is supplied, the write is directed to that file instead. If that file already exists, \fBvi\fR will not perform the write unless the "!" is supplied indicating you .I really want to destroy the older copy of the file. .IP :q[!] 16 Causes \fBvi\fR to exit. If you have modified the file you are looking at currently and haven't written it out, \fBvi\fR will refuse to exit unless the "!" is supplied. .IP ":e[!] [+[cmd]] [file]" 16 .sp 1 Start editing a new file called "file" or start editing the current file over again. The command ":e!" says "ignore the changes I've made to this file and start over from the beginning". It is useful if you really mess up the file. The optional "+" says instead of starting at the beginning, start at the "end", or, if "cmd" is supplied, execute "cmd" first. Useful cases of this are where cmd is "n" (any integer) which starts at line number n, and "/text", which searches for "text" and starts at the line where it is found. .IP "^^" 16 Switch back to the place you were before your last tag command. If your last tag command stayed within the file, ^^ returns to that tag. If you have no recent tag command, it will return to the same place in the previous file that it was showing when you switched to the current file. .IP ":n[!]" 16 Start editing the next file in the argument list. Since \fBvi\fR can be called with multiple file names, the ":n" command tells it to stop work on the current file and switch to the next file. If the current file was modifies, it has to be written out before the ":n" will work or else the "!" must be supplied, which says discard the changes I made to the current file. .IP ":n[!] file [file file ...]" 16 .sp Replace the current argument list with a new list of files and start editing the first file in this new list. .IP ":r file" 16 Read in a copy of "file" on the line after the cursor. .IP ":r !cmd" 16 Execute the "cmd" and take its output and put it into the file after the current line. .IP ":!cmd" 16 Execute any UNIX shell command. .IP ":ta[!] tag" 16 .B Vi looks in the file named .B tags in the current directory. .B Tags is a file of lines in the format: .sp 1 .ti +8 tag filename \fBvi\fR-search-command .sp 1 If \fBvi\fR finds the tag you specified in the \fB:ta\fR command, it stops editing the current file if necessary and if the current file is up to date on the disk and switches to the file specified and uses the search pattern specified to find the "tagged" item of interest. This is particularly useful when editing multi-file C programs such as the operating system. There is a program called \fBctags\fR which will generate an appropriate \fBtags\fR file for C and f77 programs so that by saying \fB:ta function\fR you will be switched to that function. It could also be useful when editing multi-file documents, though the \fBtags\fR file would have to be generated manually. .RE .NH 1 Special Arrangements for Startup .PP \fBVi\fR takes the value of \fB$TERM\fR and looks up the characteristics of that terminal in the file \fB/etc/termcap\fR. If you don't know \fBvi\fR's name for the terminal you are working on, look in \fB/etc/termcap\fR. .PP When \fBvi\fR starts, it attempts to read the variable EXINIT from your environment.* If that exists, it takes the values in it as the default values for certain of its internal constants. See the section on "Set Values" for further details. If EXINIT doesn't exist you will get all the normal defaults. .FS * On version 6 systems Instead of EXINIT, put the startup commands in the file .exrc in your home directory. .FE .PP Should you inadvertently hang up the phone while inside .B vi , or should the computer crash, all may not be lost. Upon returning to the system, type: .DS vi \-r file .DE This will normally recover the file. If there is more than one temporary file for a specific file name, \fBvi\fR recovers the newest one. You can get an older version by recovering the file more than once. The command "vi -r" without a file name gives you the list of files that were saved in the last system crash (but .I not the file just saved when the phone was hung up). .NH 1 Set Commands .LP \fBVi\fR has a number of internal variables and switches which can be set to achieve special affects. These options come in three forms, those that are switches, which toggle from off to on and back, those that require a numeric value, and those that require an alphanumeric string value. The toggle options are set by a command of the form: .DS :set option .DE and turned off with the command: .DS :set nooption .DE Commands requiring a value are set with a command of the form: .DS :set option=value .DE To display the value of a specific option type: .DS :set option? .DE To display only those that you have changed type: .DS :set .DE and to display the long table of all the settable parameters and their current values type: .DS :set all .DE .PP Most of the options have a long form and an abbreviation. Both are listed in the following table as well as the normal default value. .PP To arrange to have values other than the default used every time you enter .B vi , place the appropriate .B set command in EXINIT in your environment, e.g. .DS EXINIT='set ai aw terse sh=/bin/csh' export EXINIT .DE or .DS setenv EXINIT 'set ai aw terse sh=/bin/csh' .DE for .B sh and .B csh , respectively. These are usually placed in your .profile or .login. If you are running a system without environments (such as version 6) you can place the set command in the file .exrc in your home directory. .RS .IP autoindent\ ai 16 Default: noai Type: toggle .br When in autoindent mode, vi helps you indent code by starting each line in the same column as the preceding line. Tabbing to the right with or <^T> will move this boundary to the right, and it can be moved to the left with <^D>. .IP autoprint\ ap 16 Default: ap Type: toggle .br Causes the current line to be printed after each ex text modifying command. This is not of much interest in the normal \fBvi\fR visual mode. .IP autowrite\ aw 16 Default: noaw type: toggle .br Autowrite causes an automatic write to be done if there are unsaved changes before certain commands which change files or otherwise interact with the outside world. These commands are :!, :tag, :next, :rewind, ^^, and ^]. .IP beautify\ bf 16 Default: nobf Type: toggle .br Causes all control characters except , , and to be discarded. .IP directory\ dir 16 Default: dir=/tmp Type: string .br This is the directory in which \fBvi\fR puts its temporary file. .IP errorbells\ eb 16 Default: noeb Type: toggle .br Error messages are preceded by a . .IP hardtabs\ ht 16 Default: hardtabs=0 Type: numeric .br This option contains the value of hardware tabs in your terminal, or of software tabs expanded by the Unix system. .IP ignorecase\ ic 16 Default: noic Type: toggle .br All upper case characters are mapped to lower case in regular expression matching. .\" .IP lisp 16 .\" Default: nolisp Type: toggle .\" .br .\" Autoindent for \fBlisp\fR code. The commands \fB( ) [[\fR and \fB]]\fR .\" are modified appropriately to affect s-expressions and functions. .IP list 16 Default: nolist Type: toggle .br All printed lines have the and characters displayed visually. .IP magic 16 Default: magic Type: toggle .br Enable the metacharacters for matching. These include \fB. * < > [string] [^string]\fR and \fB[-]\fR. .IP number\ nu 16 Default: nonu Type: toggle .br Each line is displayed with its line number. .IP open 16 Default: open Type: toggle .br When set, prevents entering open or visual modes from ex or edit. Not of interest from vi. .IP optimize\ opt 16 Default: opt Type: toggle .br Basically of use only when using the \fBex\fR capabilities. This option prevents automatic s from taking place, and speeds up output of indented lines, at the expense of losing typeahead on some versions of UNIX. .IP paragraphs\ para 16 Default: para=IPLPPPQPP LIpplpipbp Type: string .br Each pair of characters in the string indicate \fBnroff\fR macros which are to be treated as the beginning of a paragraph for the \fB{\fR and \fB}\fR commands. The default string is for the \fB-ms\fR and \fB-mm\fR macros. To indicate one letter \fBnroff\fR macros, such as \fB.P\fR or \fB.H\fR, quote a space in for the second character position. For example: .sp 1 .ti +8 :set paragraphs=P\e bp .sp 1 would cause \fBvi\fR to consider \fB.P\fR and \fB.bp\fR as paragraph delimiters. .IP prompt 16 Default: prompt Type: toggle .br In .B ex command mode the prompt character \fB:\fR will be printed when \fBex\fR is waiting for a command. This is not of interest from vi. .IP redraw 16 Default: noredraw Type: toggle .br On dumb terminals, force the screen to always be up to date, by sending great amounts of output. Useful only at high speeds. .IP report 16 Default: report=5 Type: numeric .br This sets the threshold for the number of lines modified. When more than this number of lines are modified, removed, or yanked, \fBvi\fR will report the number of lines changed at the bottom of the screen. .IP scroll 16 Default: scroll={1/2 window} Type: numeric .br This is the number of lines that the screen scrolls up or down when using the <^U> and <^D> commands. .IP sections 16 Default: sections=NHSHH HUnhsh Type: string .br Each two character pair of this string specify \fBnroff\fR macro names which are to be treated as the beginning of a section by the \fB]]\fR and \fB[[\fR commands. The default string is for the \fB-ms\fR and \fB-mm\fR macros. To enter one letter \fBnroff\fR macros, use a quoted space as the second character. See \fBparagraphs\fR for a fuller explanation. .IP shell\ sh 16 Default: sh=from environment SHELL or /bin/sh Type: string .br This is the name of the \fBsh\fR to be used for "escaped" commands. .IP shiftwidth\ sw 16 Default: sw=8 Type: numeric .br This is the number of spaces that a <^T> or <^D> will move over for indenting, and the amount < and > shift by. .IP showmatch\ sm 16 Default: nosm Type: toggle .br When a \fB)\fR or \fB}\fR is typed, show the matching \fB(\fR or \fB{\fR by moving the cursor to it for one second if it is on the current screen. .\" .IP slowopen\ slow 16 .\" Default: terminal dependent Type: toggle .\" .br .\" On terminals that are slow and unintelligent, this option prevents the .\" updating of the screen some of the time to improve speed. .IP tabstop\ ts 16 Default: ts=8 Type: numeric .br s are expanded to boundaries that are multiples of this value. .IP taglength\ tl 16 Default: tl=0 Type: numeric .br If nonzero, tag names are only significant to this many characters. .IP term 16 Default: (from environment \fBTERM\fP, else dumb) Type: string .br This is the terminal and controls the visual displays. It cannot be changed when in "visual" mode, you have to Q to command mode, type a set term command, and do ``vi.'' to get back into visual. Or exit vi, fix $TERM, and reenter. The definitions that drive a particular terminal type are found in the file \fB/etc/termcap\fR. .\" .IP terse 16 .\" Default: terse Type: toggle .\" .br .\" When set, the error diagnostics are short. .IP warn 16 Default: warn Type: toggle .br The user is warned if she/he tries to escape to the shell without writing out the current changes. .IP window 16 Default: window={8 at 600 baud or less, 16 at 1200 baud, and screen size \- 1 at 2400 baud or more} Type: numeric .br This is the number of lines in the window whenever \fBvi\fR must redraw an entire screen. It is useful to make this size smaller if you are on a slow line. .IP w300,\ w1200,\ w9600 .br These set window, but only within the corresponding speed ranges. They are useful in an EXINIT to fine tune window sizes. For example, .DS set w300=4 w1200=12 .DE causes a 4 lines window at speed up to 600 baud, a 12 line window at 1200 baud, and a full screen (the default) at over 1200 baud. .IP wrapscan\ ws 16 Default: ws Type: toggle .br Searches will wrap around the end of the file when is option is set. When it is off, the search will terminate when it reaches the end or the beginning of the file. .IP wrapmargin\ wm 16 Default: wm=0 Type: numeric .br \fBVi\fR will automatically insert a when it finds a natural break point (usually a between words) that occurs within "wm" spaces of the right margin. Therefore with "wm=0" the option is off. Setting it to 10 would mean that any time you are within 10 spaces of the right margin \fBvi\fR would be looking for a or which it could replace with a . This is convenient for people who forget to look at the screen while they type. (In version 3, wrapmargin behaves more like nroff, in that the boundary specified by the distance from the right edge of the screen is taken as the rightmost edge of the area where a break is allowed, instead of the leftmost edge.) .IP writeany\ wa 16 Default: nowa Type: toggle .br \fBVi\fR normally makes a number of checks before it writes out a file. This prevents the user from inadvertently destroying a file. When the "writeany" option is enabled, \fBvi\fR no longer makes these checks. .RE ================================================ FILE: docs/USD.doc/vitut/vi.chars ================================================ .\" $OpenBSD: vi.chars,v 1.6 2004/11/29 06:20:03 jsg Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)vi.chars 8.3 (Berkeley) 6/27/96 .\" .\" .bd S 3 .pn 21 .de iP .IP "\fB\\$1\fR" \\$2 .. .SH Appendix: character functions .PP This appendix gives the uses the editor makes of each character. The characters are presented in their order in the \s-2ASCII\s0 character set: Control characters come first, then most special characters, then the digits, upper and then lower case characters. .PP For each character we tell a meaning it has as a command and any meaning it has during an insert. If it has only meaning as a command, then only this is discussed. Section numbers in parentheses indicate where the character is discussed; a `f' after the section number means that the character is mentioned in a footnote. .iP "^@" 15 Not a command character. If typed as the first character of an insertion it is replaced with the last text inserted, and the insert terminates. Only 128 characters are saved from the last insert; if more characters were inserted the mechanism is not available. A \fB^@\fR cannot be part of the file due to the editor implementation (7.5f). .iP "^A" 15 Search forward for the current word. .iP "^B" 15 Backward window. A count specifies repetition. Two lines of continuity are kept if possible (2.1, 6.1, 7.2). .iP "^C" 15 Unused. .iP "^D" 15 As a command, scrolls down a half-window of text. A count gives the number of (logical) lines to scroll, and is remembered for future \fB^D\fR and \fB^U\fR commands (2.1, 7.2). During an insert, backtabs over \fIautoindent\fR whitespace at the beginning of a line (6.6, 7.5); this whitespace cannot be backspaced over. .iP "^E" 15 Exposes one more line below the current screen in the file, leaving the cursor where it is if possible. (Version 3 only.) .iP "^F" 15 Forward window. A count specifies repetition. Two lines of continuity are kept if possible (2.1, 6.1, 7.2). .iP "^G" 15 Equivalent to \fB:f\fR\s-2CR\s0, printing the current file, whether it has been modified, the current line number and the number of lines in the file, and the percentage of the way through the file that you are. .iP "^H (\fR\s-2BS\s0\fP)" 15 Same as .B "left arrow" . (See .B h ). During an insert, eliminates the last input character, backing over it but not erasing it; it remains so you can see what you typed if you wish to type something only slightly different (3.1, 7.5). .iP "^I\ (\fR\s-2TAB\s0\fP)" 15 Not a command character. When inserted it prints as some number of spaces. When the cursor is at a tab character it rests at the last of the spaces which represent the tab. The spacing of tabstops is controlled by the \fItabstop\fR option (4.1, 6.6). .iP "^J\ (\fR\s-2LF\s0\fP)" 15 Same as .B "down arrow" (see .B j ). .iP "^K" 15 Unused. .iP "^L" 15 The \s-2ASCII\s0 formfeed character, this causes the screen to be cleared and redrawn. This is useful after a transmission error, if characters typed by a program other than the editor scramble the screen, or after output is stopped by an interrupt (5.4, 7.2f). .ne 1i .iP "^M\ (\fR\s-2CR\s0\fP)" 15 A carriage return advances to the next line, at the first non-white position in the line. Given a count, it advances that many lines (2.3). During an insert, a \s-2CR\s0 causes the insert to continue onto another line (3.1). .iP "^N" 15 Same as .B "down arrow" (see .B j ). .iP "^O" 15 Unused. .iP "^P" 15 Same as .B "up arrow" (see .B k ). .iP "^Q" 15 Not a command character. In input mode, .B ^Q quotes the next character, the same as .B ^V , except that some teletype drivers will eat the .B ^Q so that the editor never sees it. .iP "^R" 15 Redraws the current screen, eliminating logical lines not corresponding to physical lines (lines with only a single @ character on them) .\" On hardcopy terminals in \fIopen\fR mode, retypes the current line (5.4, 7.2, 7.8). .iP "^S" 15 Unused. Some teletype drivers use .B ^S to suspend output until .B ^Q is pressed. .iP "^T" 15 Not a command character. During an insert, with \fIautoindent\fR set and at the beginning of the line, inserts \fIshiftwidth\fR whitespace. .iP "^U" 15 Scrolls the screen up, inverting \fB^D\fR which scrolls down. Counts work as they do for \fB^D\fR, and the previous scroll amount is common to both. On a dumb terminal, \fB^U\fR will often necessitate clearing and redrawing the screen further back in the file (2.1, 7.2). .iP "^V" 15 Not a command character. In input mode, quotes the next character so that it is possible to insert non-printing and special characters into the file (4.2, 7.5). .iP "^W" 15 Not a command character. During an insert, backs up as \fBb\fR would in command mode; the deleted characters remain on the display (see \fB^H\fR) (7.5). .iP "^X" 15 Unused. .iP "^Y" 15 Exposes one more line above the current screen, leaving the cursor where it is if possible. (No mnemonic value for this key; however, it is next to \fB^U\fR which scrolls up a bunch.) (Version 3 only.) .iP "^Z" 15 If supported by the Unix system, stops the editor, exiting to the top level shell. Same as \fB:stop\fP\s-2CR\s0. Otherwise, unused. .iP "^[\ (\fR\s-2ESC\s0\fP)" 15 Cancels a partially formed command, such as a \fBz\fR when no following character has yet been given; terminates inputs on the last line (read by commands such as \fB: /\fR and \fB?\fR); ends insertions of new text into the buffer. If an \s-2ESC\s0 is given when quiescent in command state, the editor rings the bell or flashes the screen. You can thus hit \s-2ESC\s0 if you don't know what is happening till the editor rings the bell. If you don't know if you are in insert mode you can type \s-2ESC\s0\fBa\fR, and then material to be input; the material will be inserted correctly whether or not you were in insert mode when you started (1.5, 3.1, 7.5). .iP "^\e" 15 Unused. .iP "^]" 15 Searches for the word which is after the cursor as a tag. Equivalent to typing \fB:ta\fR, this word, and then a \s-2CR\s0. Mnemonically, this command is ``go right to'' (7.3). .iP "^^" 15 Equivalent to \fB:e #\fR\s-2CR\s0, returning to the previous position in the last edited file, or editing a file which you specified if you got a `No write since last change diagnostic' and do not want to have to type the file name again (7.3). (You have to do a \fB:w\fR before \fB^^\fR will work in this case. If you do not wish to write the file you should do \fB:e!\ #\fR\s-2CR\s0 instead.) .iP "^_" 15 Unused. Reserved as the command character for the Tektronix 4025 and 4027 terminal. .iP "\fR\s-2SPACE\s0\fP" 15 Same as .B "right arrow" (see .B l ). .iP "!" 15 An operator, which processes lines from the buffer with reformatting commands. Follow \fB!\fR with the object to be processed, and then the command name terminated by \s-2CR\s0. Doubling \fB!\fR and preceding it by a count causes count lines to be filtered; otherwise the count is passed on to the object after the \fB!\fR. Thus \fB2!}\fR\fIfmt\fR\s-2CR\s0 reformats the next two paragraphs by running them through the program \fIfmt\fR. If you are working on \s-2LISP\s0, the command \fB!%\fR\fIgrind\fR\s-2CR\s0,* .FS *Both .I fmt and .I grind are Berkeley programs and may not be present at all installations. .FE given at the beginning of a function, will run the text of the function through the \s-2LISP\s0 grinder (6.7, 7.3). To read a file or the output of a command into the buffer use \fB:r\fR (7.3). To simply execute a command use \fB:!\fR (7.3). .tr " .iP  15 Precedes a named buffer specification. There are named buffers \fB1\-9\fR used for saving deleted text and named buffers \fBa\-z\fR into which you can place text (4.3, 6.3) .tr  .iP "#" 15 The macro character which, when followed by a number, will substitute for a function key on terminals without function keys (6.9). In input mode, if this is your erase character, it will delete the last character you typed in input mode, and must be preceded with a \fB\e\fR to insert it, since it normally backs over the last input character you gave. .iP "$" 15 Moves to the end of the current line. If you \fB:se list\fR\s-2CR\s0, then the end of each line will be shown by printing a \fB$\fR after the end of the displayed text in the line. Given a count, advances to the count'th following end of line; thus \fB2$\fR advances to the end of the following line. .iP "%" 15 Moves to the parenthesis or brace \fB{ }\fR which balances the parenthesis or brace at the current cursor position. .iP "&" 15 A synonym for \fB:&\fR\s-2CR\s0, by analogy with the .I ex .B & command. .iP "\(aa" 15 When followed by a \fB\(aa\fR returns to the previous context at the beginning of a line. The previous context is set whenever the current line is moved in a non-relative way. When followed by a letter \fBa\fR\-\fBz\fR, returns to the line which was marked with this letter with a \fBm\fR command, at the first non-white character in the line. (2.2, 5.3). When used with an operator such as \fBd\fR, the operation takes place over complete lines; if you use \fB\(ga\fR, the operation takes place from the exact marked place to the current cursor position within the line. .iP "(" 15 Retreats to the beginning of a sentence. .\" or to the beginning of a \s-2LISP\s0 s-expression .\" if the \fIlisp\fR option is set. A sentence ends at a \fB. !\fR or \fB?\fR which is followed by either the end of a line or by two spaces. Any number of closing \fB) ] "\fR and \fB\(aa\fR characters may appear after the \fB. !\fR or \fB?\fR, and before the spaces or end of line. Sentences also begin at paragraph and section boundaries (see \fB{\fR and \fB[[\fR below). A count advances that many sentences (4.2, 6.8). .iP ")" 15 Advances to the beginning of a sentence. A count repeats the effect. See \fB(\fR above for the definition of a sentence (4.2, 6.8). .iP "*" 15 Unused. .iP "+" 15 Same as \s-2CR\s0 when used as a command. .iP "," 15 Reverse of the last \fBf F t\fR or \fBT\fR command, looking the other way in the current line. Especially useful after hitting too many \fB;\fR characters. A count repeats the search. .iP "\-" 15 Retreats to the previous line at the first non-white character. This is the inverse of \fB+\fR and \s-2RETURN\s0. If the line moved to is not on the screen, the screen is scrolled, or cleared and redrawn if this is not possible. If a large amount of scrolling would be required the screen is also cleared and redrawn, with the current line at the center (2.3). .iP "\&." 15 Repeats the last command which changed the buffer. Especially useful when deleting words or lines; you can delete some words/lines and then hit \fB.\fR to delete more and more words/lines. Given a count, it passes it on to the command being repeated. Thus after a \fB2dw\fR, \fB3.\fR deletes three words (3.3, 6.3, 7.2, 7.4). .iP "/" 15 Reads a string from the last line on the screen, and scans forward for the next occurrence of this string. The normal input editing sequences may be used during the input on the bottom line. .\" an returns to command state without ever searching. The search begins when you hit \s-2CR\s0 to terminate the pattern; the cursor moves to the beginning of the last line to indicate that the search is in progress; the search may then be terminated with a \s-2DEL\s0 or \s-2RUB\s0, or by backspacing when at the beginning of the bottom line, returning the cursor to its initial position. Searches normally wrap end-around to find a string anywhere in the buffer. .IP When used with an operator the enclosed region is normally affected. By mentioning an offset from the line matched by the pattern you can force whole lines to be affected. To do this give a pattern with a closing a closing \fB/\fR and then an offset \fB+\fR\fIn\fR or \fB\-\fR\fIn\fR. .IP To include the character \fB/\fR in the search string, you must escape it with a preceding \fB\e\fR. A \fB^\fR at the beginning of the pattern forces the match to occur at the beginning of a line only; this speeds the search. A \fB$\fR at the end of the pattern forces the match to occur at the end of a line only. More extended pattern matching is available, see section 7.4; unless you set \fBnomagic\fR in your \fI\&.exrc\fR file you will have to precede the characters \fB. [ *\fR and \fB~\fR in the search pattern with a \fB\e\fR to get them to work as you would naively expect (1.5, 2,2, 6.1, 7.2, 7.4). .iP "0" 15 Moves to the first character on the current line. Also used, in forming numbers, after an initial \fB1\fR\-\fB9\fR. .iP "1\-9" 15 Used to form numeric arguments to commands (2.3, 7.2). .iP ":" 15 A prefix to a set of commands for file and option manipulation and escapes to the system. Input is given on the bottom line and terminated with an \s-2CR\s0, and the command then executed. You can return to where you were by hitting \s-2DEL\s0 or \s-2RUB\s0 if you hit \fB:\fR accidentally (see primarily 6.2 and 7.3). .iP ";" 15 Repeats the last single character find which used \fBf F t\fR or \fBT\fR. A count iterates the basic scan (4.1). .iP "<" 15 An operator which shifts lines left one \fIshiftwidth\fR, normally 8 spaces. Like all operators, affects lines when repeated, as in \fB<<\fR. Counts are passed through to the basic object, thus \fB3<<\fR shifts three lines (6.6, 7.2). .iP "=" 15 .\" Reindents line for \s-2LISP\s0, as though they were typed in with \fIlisp\fR .\" and \fIautoindent\fR set (6.8). Display the line number. If a numerical prefix is specified, display its line number; otherwise display the line number of the last line in the file. .iP ">" 15 An operator which shifts lines right one \fIshiftwidth\fR, normally 8 spaces. Affects lines when repeated as in \fB>>\fR. Counts repeat the basic object (6.6, 7.2). .iP "?" 15 Scans backwards, the opposite of \fB/\fR. See the \fB/\fR description above for details on scanning (2.2, 6.1, 7.4). .iP "@" 15 A macro character (6.9). If this is your kill character, you must escape it with a \e to type it in during input mode, as it normally backs over the input you have given on the current line (3.1, 3.4, 7.5). .iP "A" 15 Appends at the end of line, a synonym for \fB$a\fR (7.2). .iP "B" 15 Backs up a word, where words are composed of non-blank sequences, placing the cursor at the beginning of the word. A count repeats the effect (2.4). .iP "C" 15 Changes the rest of the text on the current line; a synonym for \fBc$\fR. .iP "D" 15 Deletes the rest of the text on the current line; a synonym for \fBd$\fR. .iP "E" 15 Moves forward to the end of a word, defined as blanks and non-blanks, like \fBB\fR and \fBW\fR. A count repeats the effect. .iP "F" 15 Finds a single following character, backwards in the current line. A count repeats this search that many times (4.1). .iP "G" 15 Goes to the line number given as preceding argument, or the end of the file if no preceding count is given. The screen is redrawn with the new current line in the center if necessary (7.2). .iP "H" 15 .B "Home arrow" . Homes the cursor to the top line on the screen. If a count is given, then the cursor is moved to the count'th line on the screen. In any case the cursor is moved to the first non-white character on the line. If used as the target of an operator, full lines are affected (2.3, 3.2). .iP "I" 15 Inserts at the beginning of a line; a synonym for \fB^i\fR. .iP "J" 15 Joins together lines, supplying appropriate whitespace: one space between words, two spaces after a \fB.\fR, and no spaces at all if the first character of the joined on line is \fB)\fR. A count causes that many lines to be joined rather than the default two (6.5, 7.1f). .iP "K" 15 Unused. .iP "L" 15 Moves the cursor to the first non-white character of the last line on the screen. With a count, to the first non-white of the count'th line from the bottom. Operators affect whole lines when used with \fBL\fR (2.3). .iP "M" 15 Moves the cursor to the middle line on the screen, at the first non-white position on the line (2.3). .iP "N" 15 Scans for the next match of the last pattern given to \fB/\fR or \fB?\fR, but in the reverse direction; this is the reverse of \fBn\fR. .iP "O" 15 Opens a new line above the current line and inputs text there up to an \s-2ESC\s0. A count can be used on dumb terminals to specify a number of lines to be opened. .\" this is generally obsolete, as the \fIslowopen\fR option works better (3.1). .iP "P" 15 Puts the last deleted text back before/above the cursor. The text goes back as whole lines above the cursor if it was deleted as whole lines. Otherwise the text is inserted between the characters before and at the cursor. May be preceded by a named buffer specification \fB"\fR\fIx\fR to retrieve the contents of the buffer; buffers \fB1\fR\-\fB9\fR contain deleted material, buffers \fBa\fR\-\fBz\fR are available for general use (6.3). .iP "Q" 15 Quits from \fIvi\fR to \fIex\fR command mode. In this mode, whole lines form commands, ending with a \s-2RETURN\s0. You can give all the \fB:\fR commands; the editor supplies the \fB:\fR as a prompt (7.7). .iP "R" 15 Replaces characters on the screen with characters you type (overlay fashion). Terminates with an \s-2ESC\s0. .iP "S" 15 Changes whole lines, a synonym for \fBcc\fR. A count substitutes for that many lines. The lines are saved in the numeric buffers, and erased on the screen before the substitution begins. .iP "T" 15 Takes a single following character, locates the character before the cursor in the current line, and places the cursor just after that character. A count repeats the effect. Most useful with operators such as \fBd\fR (4.1). .iP "U" 15 Restores the current line to its state before you started changing it (3.5). .iP "V" 15 Unused. .iP "W" 15 Moves forward to the beginning of a word in the current line, where words are defined as sequences of blank/non-blank characters. A count repeats the effect (2.4). .iP "X" 15 Deletes the character before the cursor. A count repeats the effect, but only characters on the current line are deleted. .iP "Y" 15 Yanks a copy of the current line into the unnamed buffer, to be put back by a later \fBp\fR or \fBP\fR; a very useful synonym for \fByy\fR. A count yanks that many lines. May be preceded by a buffer name to put lines in that buffer (7.4). .iP "ZZ" 15 Exits the editor. (Same as \fB:x\fP\s-2CR\s0.) If any changes have been made, the buffer is written out to the current file. Then the editor quits. .iP "[[" 15 Backs up to the previous section boundary. A section begins at each macro in the \fIsections\fR option, normally a `.NH' or `.SH' and also at lines which which start with a formfeed \fB^L\fR. Lines beginning with \fB{\fR also stop \fB[[\fR; this makes it useful for looking backwards, a function at a time, in C programs .\" If the option \fIlisp\fR is set, stops at each \fB(\fR at the .\" beginning of a line, and is thus useful for moving backwards at the top .\" level \s-2LISP\s0 objects. (4.2, 6.1, 6.6, 7.2). .iP "\e" 15 Unused. .iP "]]" 15 Forward to a section boundary; see \fB[[\fR for a definition (4.2, 6.1, 6.6, 7.2). .iP "^" 15 Moves to the first non-white position on the current line (4.4). .iP "_" 15 Unused. .iP "\(ga" 15 When followed by a \fB\(ga\fR, returns to the previous context. The previous context is set whenever the current line is moved in a non-relative way. When followed by a letter \fBa\fR\-\fBz\fR, returns to the position which was marked with this letter with a \fBm\fR command. When used with an operator such as \fBd\fR, the operation takes place from the exact marked place to the current position within the line; if you use \fB\(aa\fR, the operation takes place over complete lines (2.2, 5.3). .iP "a" 15 Appends arbitrary text after the current cursor position; the insert can continue onto multiple lines by using \s-2RETURN\s0 within the insert. A count causes the inserted text to be replicated, but only if the inserted text is all on one line. The insertion terminates with an \s-2ESC\s0 (3.1, 7.2). .iP "b" 15 Backs up to the beginning of a word in the current line. A word is a sequence of alphanumerics, or a sequence of special characters. A count repeats the effect (2.4). .iP "c" 15 An operator which changes the following object, replacing it with the following input text up to an \s-2ESC\s0. If more than part of a single line is affected, the text which is changed away is saved in the numeric named buffers. If only part of the current line is affected, then the last character to be changed away is marked with a \fB$\fR. A count causes that many objects to be affected, thus both \fB3c)\fR and \fBc3)\fR change the following three sentences (7.4). .iP "d" 15 An operator which deletes the following object. If more than part of a line is affected, the text is saved in the numeric buffers. A count causes that many objects to be affected; thus \fB3dw\fR is the same as \fBd3w\fR (3.3, 3.4, 4.1, 7.4). .iP "e" 15 Advances to the end of the next word, defined as for \fBb\fR and \fBw\fR. A count repeats the effect (2.4, 3.1). .iP "f" 15 Finds the first instance of the next character following the cursor on the current line. A count repeats the find (4.1). .iP "g" 15 Unused. .sp Arrow keys .B h , .B j , .B k , .B l , and .B H . .iP "h" 15 .B "Left arrow" . Moves the cursor one character to the left. Like the other arrow keys, either .B h , the .B "left arrow" key, or one of the synonyms (\fB^H\fP) has the same effect. On v2 editors, arrow keys on certain kinds of terminals (those which send escape sequences, such as vt52, c100, or hp) cannot be used. A count repeats the effect (3.1, 7.5). .iP "i" 15 Inserts text before the cursor, otherwise like \fBa\fR (7.2). .iP "j" 15 .B "Down arrow" . Moves the cursor one line down in the same column. If the position does not exist, .I vi comes as close as possible to the same column. Synonyms include .B ^J (linefeed) and .B ^N . .iP "k" 15 .B "Up arrow" . Moves the cursor one line up. .B ^P is a synonym. .iP "l" 15 .B "Right arrow" . Moves the cursor one character to the right. \s-2SPACE\s0 is a synonym. .iP "m" 15 Marks the current position of the cursor in the mark register which is specified by the next character \fBa\fR\-\fBz\fR. Return to this position or use with an operator using \fB\(ga\fR or \fB\(aa\fR (5.3). .iP "n" 15 Repeats the last \fB/\fR or \fB?\fR scanning commands (2.2). .iP "o" 15 Opens new lines below the current line; otherwise like \fBO\fR (3.1). .iP "p" 15 Puts text after/below the cursor; otherwise like \fBP\fR (6.3). .iP "q" 15 Unused. .iP "r" 15 Replaces the single character at the cursor with a single character you type. The new character may be a \s-2RETURN\s0; this is the easiest way to split lines. A count replaces each of the following count characters with the single character given; see \fBR\fR above which is the more usually useful iteration of \fBr\fR (3.2). .iP "s" 15 Changes the single character under the cursor to the text which follows up to an \s-2ESC\s0; given a count, that many characters from the current line are changed. The last character to be changed is marked with \fB$\fR as in \fBc\fR (3.2). .iP "t" 15 Advances the cursor upto the character before the next character typed. Most useful with operators such as \fBd\fR and \fBc\fR to delete the characters up to a following character. You can use \fB.\fR to delete more if this doesn't delete enough the first time (4.1). .iP "u" 15 Undoes the last change made to the current buffer. If repeated, will alternate between these two states, thus is its own inverse. When used after an insert which inserted text on more than one line, the lines are saved in the numeric named buffers (3.5). .iP "v" 15 Unused. .iP "w" 15 Advances to the beginning of the next word, as defined by \fBb\fR (2.4). .iP "x" 15 Deletes the single character under the cursor. With a count deletes deletes that many characters forward from the cursor position, but only on the current line (6.5). .iP "y" 15 An operator, yanks the following object into the unnamed temporary buffer. If preceded by a named buffer specification, \fB"\fR\fIx\fR, the text is placed in that buffer also. Text can be recovered by a later \fBp\fR or \fBP\fR (7.4). .iP "z" 15 Redraws the screen with the current line placed as specified by the following character: \s-2RETURN\s0 specifies the top of the screen, \fB.\fR the center of the screen, and \fB\-\fR at the bottom of the screen. A count may be given after the \fBz\fR and before the following character to specify the new screen size for the redraw. A count before the \fBz\fR gives the number of the line to place in the center of the screen instead of the default current line. (5.4) .iP "{" 15 Retreats to the beginning of the beginning of the preceding paragraph. A paragraph begins at each macro in the \fIparagraphs\fR option, normally `.IP', `.LP', `.PP', `.QP' and `.bp'. A paragraph also begins after a completely empty line, and at each section boundary (see \fB[[\fR above) (4.2, 6.8, 7.6). .iP "|" 15 Places the cursor on the character in the column specified by the count (7.1, 7.2). .iP "}" 15 Advances to the beginning of the next paragraph. See \fB{\fR for the definition of paragraph (4.2, 6.8, 7.6). .iP "~" 15 Unused. .iP "^?\ (\s-2\fRDEL\fP\s0)" 15 Interrupts the editor, returning it to command accepting state (1.5, 7.5). ================================================ FILE: docs/USD.doc/vitut/vi.in ================================================ .\" $OpenBSD: vi.in,v 1.9 2022/12/26 19:16:03 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)vi.in 8.5 (Berkeley) 8/18/96 .\" .if n \{\ .po 5n .ll 70n .\} .nr LL 6.5i .nr FL 6.5i .EH 'USD:11-%''An Introduction to Display Editing with Vi' .OH 'An Introduction to Display Editing with Vi''USD:11-%' .\" .bd S 3 .if t .ds dg \(dg .if n .ds dg + .if t .ds dd \(dd .if n .ds dd ++ .\".RP .TL An Introduction to Display Editing with Vi .AU William Joy .AU Mark Horton .AI Computer Science Division Department of Electrical Engineering and Computer Science University of California, Berkeley Berkeley, Ca. 94720 .AB .PP .I Vi (visual) is a display oriented interactive text editor. When using .I vi the screen of your terminal acts as a window into the file which you are editing. Changes which you make to the file are reflected in what you see. .PP Using .I vi you can insert new text any place in the file quite easily. Most of the commands to .I vi move the cursor around in the file. There are commands to move the cursor forward and backward in units of characters, words, sentences and paragraphs. A small set of operators, like .B d for delete and .B c for change, are combined with the motion commands to form operations such as delete word or change paragraph, in a simple and natural way. This regularity and the mnemonic assignment of commands to keys makes the editor command set easy to remember and to use. .PP .I Vi will work on a large number of display terminals, and new terminals are easily driven after editing a terminal description file. While it is advantageous to have an intelligent terminal which can locally insert and delete lines and characters from the display, the editor will function quite well on dumb terminals over slow phone lines. The editor makes allowance for the low bandwidth in these situations and uses smaller window sizes and different display updating algorithms to make best use of the limited speed available. .PP It is also possible to use the command set of .I vi on hardcopy terminals, storage tubes and ``glass tty's'' using a one-line editing window; thus .I vi 's command set is available on all terminals. The full command set of the more traditional, line oriented editor .I ex is available within .I vi ; it is quite simple to switch between the two modes of editing. .AE .NH 1 Getting started .PP .FS The financial support of an \s-2IBM\s0 Graduate Fellowship and the National Science Foundation under grants MCS74-07644-A03 and MCS78-07291 is gratefully acknowledged. .FE This document provides a quick introduction to .I vi . (Pronounced \fIvee-eye\fP.) You should be running .I vi on a file you are familiar with while you are reading this. The first part of this document (sections 1 through 5) describes the basics of using .I vi . Some topics of special interest are presented in section 6, and some nitty-gritty details of how the editor functions are saved for section 7 to avoid cluttering the presentation here. .PP There is also a short appendix here, which gives for each character the special meanings which this character has in \fIvi\fR. Attached to this document should be a quick reference card. This card summarizes the commands of .I vi in a very compact format. You should have the card handy while you are learning .I vi . .NH 2 Specifying terminal type .PP Before you start .I vi you can tell the system what kind of terminal you are using. Here is a (necessarily incomplete) list of terminal type codes. If your terminal does not appear here, you should consult with one of the staff members on your system to find out the code for your terminal. If your terminal does not have a code, one can be assigned and a description for the terminal can be created. .LP .TS center; ab ab ab a a a. Code Full name Type _ 2621 Hewlett-Packard 2621A/P Intelligent 2645 Hewlett-Packard 264x Intelligent act4 Microterm ACT-IV Dumb act5 Microterm ACT-V Dumb adm3a Lear Siegler ADM-3a Dumb adm31 Lear Siegler ADM-31 Intelligent c100 Human Design Concept 100 Intelligent dm1520 Datamedia 1520 Dumb dm2500 Datamedia 2500 Intelligent dm3025 Datamedia 3025 Intelligent fox Perkin-Elmer Fox Dumb h1500 Hazeltine 1500 Intelligent h19 Heathkit h19 Intelligent i100 Infoton 100 Intelligent mime Imitating a smart act4 Intelligent t1061 Teleray 1061 Intelligent vt52 Dec VT-52 Dumb .TE .PP Suppose for example that you have a Hewlett-Packard HP2621A terminal. The code used by the system for this terminal is `2621'. In this case you can use one of the following commands to tell the system the type of your terminal: .DS I % setenv TERM 2621 .DE This command works with the .I csh shell. If you are using the standard Bourne shell .I sh then you should give the command: .DS I $ export TERM=2621 .DE .PP If you want to arrange to have your terminal type set up automatically when you log in, you can use the .I tset (1) program. If you dial in on a .I mime , but often use hardwired ports, a typical line for your .I .login file (if you use csh) would be .DS I % setenv TERM `tset - -d mime` .DE or for your .I .profile file (if you use sh) .DS I $ export TERM=`tset - -d mime` .DE .I Tset knows which terminals are hardwired to each port and needs only to be told that when you dial in you are probably on a .I mime . .I Tset is usually used to change the erase and kill characters, too. .NH 2 Editing a file .PP After telling the system which kind of terminal you have, you should make a copy of a file you are familiar with, and run .I vi on this file, giving the command .DS I % vi name .DE replacing \fIname\fR with the name of the copy file you just created. The screen should clear and the text of your file should appear on the screen. If something else happens refer to the footnote.\*(dd .FS \*(dd If you gave the system an incorrect terminal type code then the editor may have just made a mess out of your screen. This happens when it sends control codes for one kind of terminal to some other kind of terminal. In this case hit the keys \fB:q\fR (colon and the q key) and then hit the \s-2RETURN\s0 key. This should get you back to the command level interpreter. Figure out what you did wrong (ask someone else if necessary) and try again. Another thing which can go wrong is that you typed the wrong file name and the editor just printed an error diagnostic. In this case you should follow the above procedure for getting out of the editor and try again, this time spelling the file name correctly. If the editor doesn't seem to respond to the commands which you type here, try sending an interrupt to it by hitting the \s-2DEL\s0 or \s-2RUB\s0 key on your terminal, and then hitting the \fB:q\fR command again followed by a carriage return. .sp .FE .NH 2 The editor's copy: the buffer .PP The editor does not directly modify the file which you are editing. Rather, the editor makes a copy of this file, in a place called the .I buffer , and remembers the file's name. You do not affect the contents of the file unless and until you write the changes you make back into the original file. .NH 2 Notational conventions .PP In our examples, input which must be typed as is will be presented in \fBbold face\fR. Text which should be replaced with appropriate input will be given in \fIitalics\fR. We will represent special characters in \s-2SMALL CAPITALS\s0. .NH 2 Arrow keys .PP The editor command set is independent of the terminal you are using. On most terminals with cursor positioning keys, these keys will also work within the editor. If you don't have cursor positioning keys, or even if you do, you can use the \fBh j k\fR and \fBl\fR keys as cursor positioning keys (these are labelled with arrows on an .I adm3a). * .PP (Particular note for the HP2621: on this terminal the function keys must be \fIshifted\fR (ick) to send to the machine, otherwise they only act locally. Unshifted use will leave the cursor positioned incorrectly.) .FS * As we will see later, .B h moves back to the left (like control-h which is a backspace), .B j moves down (in the same column), .B k moves up (in the same column), and .B l moves to the right. .FE .NH 2 Special characters: \s-2ESC\s0, \s-2CR\s0 and \s-2DEL\s0 .PP Several of these special characters are very important, so be sure to find them right now. Look on your keyboard for a key labelled \s-2ESC\s0 or \s-2ALT\s0. It should be near the upper left corner of your terminal. Try hitting this key a few times. The editor will ring the bell to indicate that it is in a quiescent state.\*(dd .FS \*(dd On smart terminals where it is possible, the editor will quietly flash the screen rather than ringing the bell. .FE Partially formed commands are cancelled by \s-2ESC\s0, and when you insert text in the file you end the text insertion with \s-2ESC\s0. This key is a fairly harmless one to hit, so you can just hit it if you don't know what is going on until the editor rings the bell. .PP The \s-2CR\s0 or \s-2RETURN\s0 key is important because it is used to terminate certain commands. It is usually at the right side of the keyboard, and is the same command used at the end of each shell command. .PP Another very useful key is the \s-2DEL\s0 or \s-2RUB\s0 key, which generates an interrupt, telling the editor to stop what it is doing. It is a forceful way of making the editor listen to you, or to return it to the quiescent state if you don't know or don't like what is going on. Try hitting the `/' key on your terminal. This key is used when you want to specify a string to be searched for. The cursor should now be positioned at the bottom line of the terminal after a `/' printed as a prompt. You can get the cursor back to the current position by hitting the \s-2DEL\s0 or \s-2RUB\s0 key; try this now.* .FS * Backspacing over the `/' will also cancel the search. .FE From now on we will simply refer to hitting the \s-2DEL\s0 or \s-2RUB\s0 key as ``sending an interrupt.''** .FS ** On some systems, this interruptibility comes at a price: you cannot type ahead when the editor is computing with the cursor on the bottom line. .FE .PP The editor often echoes your commands on the last line of the terminal. If the cursor is on the first position of this last line, then the editor is performing a computation, such as computing a new position in the file after a search or running a command to reformat part of the buffer. When this is happening you can stop the editor by sending an interrupt. .NH 2 Getting out of the editor .PP After you have worked with this introduction for a while, and you wish to do something else, you can give the command \fBZZ\fP to the editor. This will write the contents of the editor's buffer back into the file you are editing, if you made any changes, and then quit from the editor. You can also end an editor session by giving the command \fB:q!\fR\s-2CR\s0;\*(dg .FS \*(dg All commands which read from the last display line can also be terminated with a \s-2ESC\s0 as well as an \s-2CR\s0. .FE this is a dangerous but occasionally essential command which ends the editor session and discards all your changes. You need to know about this command in case you change the editor's copy of a file you wish only to look at. Be very careful not to give this command when you really want to save the changes you have made. .NH 1 Moving around in the file .NH 2 Scrolling and paging .PP The editor has a number of commands for moving around in the file. The most useful of these is generated by hitting the control and D keys at the same time, a control-D or `^D'. We will use this two character notation for referring to these control keys from now on. .\" You may have a key labelled `^' on your terminal; .\" `^' is exclusively used as part of the `^x' notation for control characters. .PP As you know now if you tried hitting \fB^D\fR, this command scrolls down in the file. The \fBD\fR thus stands for down. Many editor commands are mnemonic and this makes them much easier to remember. For instance the command to scroll up is \fB^U\fR. Many dumb terminals can't scroll up at all, in which case hitting \fB^U\fR clears the screen and refreshes it with a line which is farther back in the file at the top. .PP If you want to see more of the file below where you are, you can hit \fB^E\fR to expose one more line at the bottom of the screen, leaving the cursor where it is. The command \fB^Y\fR (which is hopelessly non-mnemonic, but next to \fB^U\fR on the keyboard) exposes one more line at the top of the screen. .PP There are other ways to move around in the file; the keys \fB^F\fR and \fB^B\fR move forward and backward a page, keeping a couple of lines of continuity between screens so that it is possible to read through a file using these rather than \fB^D\fR and \fB^U\fR if you wish. .PP Notice the difference between scrolling and paging. If you are trying to read the text in a file, hitting \fB^F\fR to move forward a page will leave you only a little context to look back at. Scrolling on the other hand leaves more context, and happens more smoothly. You can continue to read the text as scrolling is taking place. .NH 2 Searching, goto, and previous context .PP Another way to position yourself in the file is by giving the editor a string to search for. Type the character \fB/\fR followed by a string of characters terminated by \s-2CR\s0. The editor will position the cursor at the next occurrence of this string. Try hitting \fBn\fR to then go to the next occurrence of this string. The character \fB?\fR will search backwards from where you are, and is otherwise like \fB/\fR.\*(dg .FS \*(dg These searches will normally wrap around the end of the file, and thus find the string even if it is not on a line in the direction you search provided it is anywhere else in the file. You can disable this wraparound in scans by giving the command \fB:se nowrapscan\fR\s-2CR\s0, or more briefly \fB:se nows\fR\s-2CR\s0. .FE .PP If the search string you give the editor is not present in the file, the editor will print a diagnostic on the last line of the screen, and the cursor will be returned to its initial position. .PP If you wish the search to match only at the beginning of a line, begin the search string with an \fB^\fR. To match only at the end of a line, end the search string with a \fB$\fR. Thus \fB/^search\fR\s-2CR\s0 will search for the word `search' at the beginning of a line, and \fB/last$\fR\s-2CR\s0 searches for the word `last' at the end of a line.* .FS *Actually, the string you give to search for here can be a .I "regular expression" in the sense of the editors .I ex (1) and .I ed (1). If you don't wish to learn about this yet, you can disable this more general facility by doing \fB:se\ nomagic\fR\s-2CR\s0; by putting this command in EXINIT in your environment, you can have this always be in effect (more about .I EXINIT later.) .FE .PP The command \fBG\fR, when preceded by a number will position the cursor at that line in the file. Thus \fB1G\fR will move the cursor to the first line of the file. If you give \fBG\fR no count, then it moves to the end of the file. .PP If you are near the end of the file, and the last line is not at the bottom of the screen, the editor will place only the character `~' on each remaining line. This indicates that the last line in the file is on the screen; that is, the `~' lines are past the end of the file. .PP You can find out the state of the file you are editing by typing a \fB^G\fR. The editor will show you the name of the file you are editing, the number of the current line, the number of lines in the buffer, and the percentage of the way through the buffer which you are. Try doing this now, and remember the number of the line you are on. Give a \fBG\fR command to get to the end and then another \fBG\fR command to get back where you were. .PP You can also get back to a previous position by using the command \fB\(ga\(ga\fR (two back quotes). This is often more convenient than \fBG\fR because it requires no advance preparation. Try giving a \fBG\fR or a search with \fB/\fR or \fB?\fR and then a \fB\(ga\(ga\fR to get back to where you were. If you accidentally hit \fBn\fR or any command which moves you far away from a context of interest, you can quickly get back by hitting \fB\(ga\(ga\fR. .NH 2 Moving around on the screen .PP Now try just moving the cursor around on the screen. If your terminal has arrow keys (4 or 5 keys with arrows going in each direction) try them and convince yourself that they work. If you don't have working arrow keys, you can always use .B h , .B j , .B k , and .B l . Experienced users of .I vi prefer these keys to arrow keys, because they are usually right underneath their fingers. .PP Hit the \fB+\fR key. Each time you do, notice that the cursor advances to the next line in the file, at the first non-white position on the line. The \fB\-\fR key is like \fB+\fR but goes the other way. .PP These are very common keys for moving up and down lines in the file. Notice that if you go off the bottom or top with these keys then the screen will scroll down (and up if possible) to bring a line at a time into view. The \s-2RETURN\s0 key has the same effect as the \fB+\fR key. .PP .I Vi also has commands to take you to the top, middle and bottom of the screen. \fBH\fR will take you to the top (home) line on the screen. Try preceding it with a number as in \fB3H\fR. This will take you to the third line on the screen. Many .I vi commands take preceding numbers and do interesting things with them. Try \fBM\fR, which takes you to the middle line on the screen, and \fBL\fR, which takes you to the last line on the screen. \fBL\fR also takes counts, thus \fB5L\fR will take you to the fifth line from the bottom. .NH 2 Moving within a line .PP Now try picking a word on some line on the screen, not the first word on the line. move the cursor using \s-2RETURN\s0 and \fB\-\fR to be on the line where the word is. Try hitting the \fBw\fR key. This will advance the cursor to the next word on the line. Try hitting the \fBb\fR key to back up words in the line. Also try the \fBe\fR key which advances you to the end of the current word rather than to the beginning of the next word. Also try \s-2SPACE\s0 (the space bar) which moves right one character and the \s-2BS\s0 (backspace or \fB^H\fR) key which moves left one character. The key \fBh\fR works as \fB^H\fR does and is useful if you don't have a \s-2BS\s0 key. (Also, as noted just above, \fBl\fR will move to the right.) .PP If the line had punctuation in it you may have noticed that that the \fBw\fR and \fBb\fR keys stopped at each group of punctuation. You can also go back and forwards words without stopping at punctuation by using \fBW\fR and \fBB\fR rather than the lower case equivalents. Think of these as bigger words. Try these on a few lines with punctuation to see how they differ from the lower case \fBw\fR and \fBb\fR. .PP The word keys wrap around the end of line, rather than stopping at the end. Try moving to a word on a line below where you are by repeatedly hitting \fBw\fR. .NH 2 Summary .IP .TS lw(.50i)b a. \fR\s-2SPACE\s0\fP advance the cursor one position ^B backwards to previous page ^D scrolls down in the file ^E exposes another line at the bottom ^F forward to next page ^G tell what is going on ^H backspace the cursor ^N next line, same column ^P previous line, same column ^U scrolls up in the file ^Y exposes another line at the top + next line, at the beginning \- previous line, at the beginning / scan for a following string forwards ? scan backwards B back a word, ignoring punctuation G go to specified line, last default H home screen line M middle screen line L last screen line W forward a word, ignoring punctuation b back a word e end of current word n scan for next instance of \fB/\fR or \fB?\fR pattern w word after this word .TE .NH 2 View .PP If you want to use the editor to look at a file, rather than to make changes, invoke it as .I view instead of .I vi . This will set the .I readonly option which will prevent you from accidentally overwriting the file. .sp .NH 1 Making simple changes .NH 2 Inserting .PP One of the most useful commands is the \fBi\fR (insert) command. After you type \fBi\fR, everything you type until you hit \s-2ESC\s0 is inserted into the file. Try this now; position yourself to some word in the file and try inserting text before this word. If you are on an dumb terminal it will seem, for a minute, that some of the characters in your line have been overwritten, but they will reappear when you hit \s-2ESC\s0. .PP Now try finding a word which can, but does not, end in an `s'. Position yourself at this word and type \fBe\fR (move to end of word), then \fBa\fR for append and then `s\s-2ESC\s0' to terminate the textual insert. This sequence of commands can be used to easily pluralize a word. .PP Try inserting and appending a few times to make sure you understand how this works; \fBi\fR placing text to the left of the cursor, \fBa\fR to the right. .PP It is often the case that you want to add new lines to the file you are editing, before or after some specific line in the file. Find a line where this makes sense and then give the command \fBo\fR to create a new line after the line you are on, or the command \fBO\fR to create a new line before the line you are on. After you create a new line in this way, text you type up to an \s-2ESC\s0 is inserted on the new line. .PP Many related editor commands are invoked by the same letter key and differ only in that one is given by a lower case key and the other is given by an upper case key. In these cases, the upper case key often differs from the lower case key in its sense of direction, with the upper case key working backward and/or up, while the lower case key moves forward and/or down. .PP Whenever you are typing in text, you can give many lines of input or just a few characters. To type in more than one line of text, hit a \s-2RETURN\s0 at the middle of your input. A new line will be created for text, and you can continue to type. If you are on a slow and dumb terminal the editor may choose to wait to redraw the tail of the screen, and will let you type over the existing screen lines. This avoids the lengthy delay which would occur if the editor attempted to keep the tail of the screen always up to date. The tail of the screen will be fixed up, and the missing lines will reappear, when you hit \s-2ESC\s0. .PP While you are inserting new text, you can use the characters you normally use at the system command level (usually \fB^H\fR or \fB#\fR) to backspace over the last character which you typed, and the character which you use to kill input lines (usually \fB@\fR, \fB^X\fR, or \fB^U\fR) to erase the input you have typed on the current line.\*(dg .FS \*(dg In fact, the character \fB^H\fR (backspace) always works to erase the last input character here, regardless of what your erase character is. .FE The character \fB^W\fR will erase a whole word and leave you after the space after the previous word; it is useful for quickly backing up in an insert. .PP Notice that when you backspace during an insertion the characters you backspace over are not erased; the cursor moves backwards, and the characters remain on the display. This is often useful if you are planning to type in something similar. In any case the characters disappear when when you hit \s-2ESC\s0; if you want to get rid of them immediately, hit an \s-2ESC\s0 and then \fBa\fR again. .PP Notice also that you can't erase characters which you didn't insert, and that you can't backspace around the end of a line. If you need to back up to the previous line to make a correction, just hit \s-2ESC\s0 and move the cursor back to the previous line. After making the correction you can return to where you were and use the insert or append command again. .sp .5 .NH 2 Making small corrections .PP You can make small corrections in existing text quite easily. Find a single character which is wrong or just pick any character. Use the arrow keys to find the character, or get near the character with the word motion keys and then either backspace (hit the \s-2BS\s0 key or \fB^H\fR or even just \fBh\fR) or \s-2SPACE\s0 (using the space bar) until the cursor is on the character which is wrong. If the character is not needed then hit the \fBx\fP key; this deletes the character from the file. It is analogous to the way you \fBx\fP out characters when you make mistakes on a typewriter (except it's not as messy). .PP If the character is incorrect, you can replace it with the correct character by giving the command \fBr\fR\fIc\fR, where \fIc\fR is replaced by the correct character. Finally if the character which is incorrect should be replaced by more than one character, give the command \fBs\fR which substitutes a string of characters, ending with \s-2ESC\s0, for it. If there are a small number of characters which are wrong you can precede \fBs\fR with a count of the number of characters to be replaced. Counts are also useful with \fBx\fR to specify the number of characters to be deleted. .NH 2 More corrections: operators .PP You already know almost enough to make changes at a higher level. All you need to know now is that the .B d key acts as a delete operator. Try the command .B dw to delete a word. Try hitting \fB.\fR a few times. Notice that this repeats the effect of the \fBdw\fR. The command \fB.\fR repeats the last command which made a change. You can remember it by analogy with an ellipsis `\fB...\fR'. .PP Now try \fBdb\fR. This deletes a word backwards, namely the preceding word. Try \fBd\fR\s-2SPACE\s0. This deletes a single character, and is equivalent to the \fBx\fR command. .PP Another very useful operator is .B c or change. The command .B cw thus changes the text of a single word. You follow it by the replacement text ending with an \s-2ESC\s0. Find a word which you can change to another, and try this now. Notice that the end of the text to be changed was marked with the character `$' so that you can see this as you are typing in the new material. .sp .5 .NH 2 Operating on lines .PP It is often the case that you want to operate on lines. Find a line which you want to delete, and type \fBdd\fR, the .B d operator twice. This will delete the line. If you are on a dumb terminal, the editor may just erase the line on the screen, replacing it with a line with only an @ on it. This line does not correspond to any line in your file, but only acts as a place holder. It helps to avoid a lengthy redraw of the rest of the screen which would be necessary to close up the hole created by the deletion on a terminal without a delete line capability. .PP Try repeating the .B c operator twice; this will change a whole line, erasing its previous contents and replacing them with text you type up to an \s-2ESC\s0.\*(dg .FS \*(dg The command \fBS\fR is a convenient synonym for for \fBcc\fR, by analogy with \fBs\fR. Think of \fBS\fR as a substitute on lines, while \fBs\fR is a substitute on characters. .FE .PP You can delete or change more than one line by preceding the .B dd or .B cc with a count, i.e. \fB5dd\fR deletes 5 lines. You can also give a command like \fBdL\fR to delete all the lines up to and including the last line on the screen, or \fBd3L\fR to delete through the third from the bottom line. Try some commands like this now.* .FS * One subtle point here involves using the \fB/\fR search after a \fBd\fR. This will normally delete characters from the current position to the point of the match. If what is desired is to delete whole lines including the two points, give the pattern as \fB/pat/+0\fR, a line address. .FE Notice that the editor lets you know when you change a large number of lines so that you can see the extent of the change. The editor will also always tell you when a change you make affects text which you cannot see. .NH 2 Undoing .PP Now suppose that the last change which you made was incorrect; you could use the insert, delete and append commands to put the correct material back. However, since it is often the case that we regret a change or make a change incorrectly, the editor provides a .B u (undo) command to reverse the last change which you made. Try this a few times, and give it twice in a row to notice that a .B u also undoes a .B u . .PP The undo command lets you reverse only a single change. After you make a number of changes to a line, you may decide that you would rather have the original state of the line back. The .B U command restores the current line to the state before you started changing it. Additionally, an unlimited number of changes may be reversed by following a .B u with a .B . . Each subsequent .B . will undo one more change. .PP You can recover text which you delete, even if undo will not bring it back; see the section on recovering lost text below. .NH 2 Summary .IP .TS lw(.50i)b a. \fB\s-2SPACE\s0\fP advance the cursor one position ^H backspace the cursor \fBwerase\fP (usually ^W), erase a word during an insert \fBerase\fP (usually DEL or ^H), erases a character during an insert \fBkill\fP (usually ^U), kills the insert on this line \fB.\fP repeats the changing command O opens and inputs new lines, above the current U undoes the changes you made to the current line a appends text after the cursor c changes the object you specify to the following text d deletes the object you specify i inserts text before the cursor o opens and inputs new lines, below the current u undoes the last change .TE .NH 1 Moving about; rearranging and duplicating text .NH 2 Low level character motions .PP Now move the cursor to a line where there is a punctuation or a bracketing character such as a parenthesis or a comma or period. Try the command \fBf\fR\fIx\fR, where \fIx\fR is this character. This command finds the next \fIx\fR character to the right of the cursor in the current line. Try then hitting a \fB;\fR, which finds the next instance of the same character. By using the \fBf\fR command and then a sequence of \fB;\fR's you can often get to a particular place in a line much faster than with a sequence of word motions or \s-2SPACE\s0s. There is also an \fBF\fR command, which is like \fBf\fR, but searches backward. The \fB;\fR command repeats \fBF\fR also. .PP When you are operating on the text in a line it is often desirable to deal with the characters up to, but not including, the first instance of a character. Try \fBdf\fR\fIx\fR for some \fIx\fR now and notice that the text up to (and including) the \fIx\fR character is deleted. Undo this with \fBu\fR and then try \fBdt\fR\fIx\fR; the \fBt\fR here stands for to, i.e. delete up to the next \fIx\fR, but not the \fIx\fR. The command \fBT\fR is the reverse of \fBt\fR. .PP When working with the text of a single line, a \fB^\fR moves the cursor to the first non-white position on the line, and a \fB$\fR moves it to the end of the line. Thus \fB$a\fR will append new text at the end of the current line. .PP Your file may have tab (\fB^I\fR) characters in it. These characters are represented as a number of spaces expanding to a tab stop, where tab stops are every 8 positions.* .FS * This is settable by a command of the form \fB:se ts=\fR\fIx\fR\s-2CR\s0, where \fIx\fR is 4 to set tabstops every four columns. This has an effect on the screen representation within the editor. .FE When the cursor is at a tab, it sits on the last of the several spaces which represent that tab. Try moving the cursor back and forth over tabs so you understand how this works. .PP On rare occasions, your file may have nonprinting characters in it. These characters are displayed in the same way they are represented in this document, that is with a two character code, the first character of which is `^'. On the screen non-printing characters resemble a `^' character adjacent to another, but spacing or backspacing over the character will reveal that the two characters are, like the spaces representing a tab character, a single character. .PP The editor sometimes discards control characters, depending on the character and the setting of the .I beautify option, if you attempt to insert them in your file. You can get a control character in the file by beginning an insert and then typing a \fB^V\fR before the control character. The \fB^V\fR quotes the following character, causing it to be inserted directly into the file. .PP .NH 2 Higher level text objects .PP In working with a document it is often advantageous to work in terms of sentences, paragraphs, and sections. The operations \fB(\fR and \fB)\fR move to the beginning of the previous and next sentences respectively. Thus the command \fBd)\fR will delete the rest of the current sentence; likewise \fBd(\fR will delete the previous sentence if you are at the beginning of the current sentence, or the current sentence up to where you are if you are not at the beginning of the current sentence. .PP A sentence is defined to end at a `.', `!' or `?' which is followed by either the end of a line, or by two spaces. Any number of closing `)', `]', `"' and `\(aa' characters may appear after the `.', `!' or `?' before the spaces or end of line. .PP The operations \fB{\fR and \fB}\fR move over paragraphs and the operations \fB[[\fR and \fB]]\fR move over sections.\*(dg .FS \*(dg The \fB[[\fR and \fB]]\fR operations require the operation character to be doubled because they can move the cursor far from where it currently is. While it is easy to get back with the command \fB\(ga\(ga\fP, these commands would still be frustrating if they were easy to hit accidentally. .FE .PP A paragraph begins after each empty line, and also at each of a set of paragraph macros, specified by the pairs of characters in the definition of the string valued option \fIparagraphs\fR. The default setting for this option defines the paragraph macros of the \fI\-ms\fR and \fI\-mm\fR macro packages, i.e. the `.IP', `.LP', `.PP' and `.QP', `.P' and `.LI' macros.\*(dd .FS \*(dd You can easily change or extend this set of macros by assigning a different string to the \fIparagraphs\fR option in your EXINIT. See section 6.2 for details. The `.bp' directive is also considered to start a paragraph. .FE Each paragraph boundary is also a sentence boundary. The sentence and paragraph commands can be given counts to operate over groups of sentences and paragraphs. .PP Sections in the editor begin after each macro in the \fIsections\fR option, normally `.NH', `.SH', `.H' and `.HU', and each line with a formfeed \fB^L\fR in the first column. Section boundaries are always line and paragraph boundaries also. .PP Try experimenting with the sentence and paragraph commands until you are sure how they work. If you have a large document, try looking through it using the section commands. The section commands interpret a preceding count as a different window size in which to redraw the screen at the new location, and this window size is the base size for newly drawn windows until another size is specified. This is very useful if you are on a slow terminal and are looking for a particular section. You can give the first section command a small count to then see each successive section heading in a small window. .NH 2 Rearranging and duplicating text .PP The editor has a single unnamed buffer where the last deleted or changed away text is saved, and a set of named buffers \fBa\fR\-\fBz\fR which you can use to save copies of text and to move text around in your file and between files. .PP The operator .B y yanks a copy of the object which follows into the unnamed buffer. If preceded by a buffer name, \fB"\fR\fIx\fR\|\fBy\fR, where \fIx\fR here is replaced by a letter \fBa\-z\fR, it places the text in the named buffer. The text can then be put back in the file with the commands .B p and .B P ; \fBp\fR puts the text after or below the cursor, while \fBP\fR puts the text before or above the cursor. .PP If the text which you yank forms a part of a line, or is an object such as a sentence which partially spans more than one line, then when you put the text back, it will be placed after the cursor (or before if you use \fBP\fR). If the yanked text forms whole lines, they will be put back as whole lines, without changing the current line. In this case, the put acts much like an \fBo\fR or \fBO\fR command. .PP Try the command \fBYP\fR. This makes a copy of the current line and leaves you on this copy, which is placed before the current line. The command \fBY\fR is a convenient abbreviation for \fByy\fR. The command \fBYp\fR will also make a copy of the current line, and place it after the current line. You can give \fBY\fR a count of lines to yank, and thus duplicate several lines; try \fB3YP\fR. .PP To move text within the buffer, you need to delete it in one place, and put it back in another. You can precede a delete operation by the name of a buffer in which the text is to be stored as in \fB"a5dd\fR deleting 5 lines into the named buffer \fIa\fR. You can then move the cursor to the eventual resting place of these lines and do a \fB"ap\fR or \fB"aP\fR to put them back. In fact, you can switch and edit another file before you put the lines back, by giving a command of the form \fB:e \fR\fIname\fR\s-2CR\s0 where \fIname\fR is the name of the other file you want to edit. You will have to write back the contents of the current editor buffer (or discard them) if you have made changes before the editor will let you switch to the other file. An ordinary delete command saves the text in the unnamed buffer, so that an ordinary put can move it elsewhere. However, the unnamed buffer is lost when you change files, so to move text from one file to another you should use a named buffer. .NH 2 Summary. .IP .TS lw(.50i)b a. ^ first non-white on line $ end of line ) forward sentence } forward paragraph ]] forward section ( backward sentence { backward paragraph [[ backward section f\fIx\fR find \fIx\fR forward in line p put text back, after cursor or below current line y yank operator, for copies and moves t\fIx\fR up to \fIx\fR forward, for operators F\fIx\fR f backward in line P put text back, before cursor or above current line T\fIx\fR t backward in line .TE .ne 1i .NH 1 High level commands .NH 2 Writing, quitting, editing new files .PP So far we have seen how to enter .I vi and to write out our file using either \fBZZ\fR or \fB:w\fR\s-2CR\s0. The first exits from the editor, (writing if changes were made), the second writes and stays in the editor. .PP If you have changed the editor's copy of the file but do not wish to save your changes, either because you messed up the file or decided that the changes are not an improvement to the file, then you can give the command \fB:q!\fR\s-2CR\s0 to quit from the editor without writing the changes. You can also reedit the same file (starting over) by giving the command \fB:e!\fR\s-2CR\s0. These commands should be used only rarely, and with caution, as it is not possible to recover the changes you have made after you discard them in this manner. .PP You can edit a different file without leaving the editor by giving the command \fB:e\fR\ \fIname\fR\s-2CR\s0. If you have not written out your file before you try to do this, then the editor will tell you this, and delay editing the other file. You can then give the command \fB:w\fR\s-2CR\s0 to save your work and then the \fB:e\fR\ \fIname\fR\s-2CR\s0 command again, or carefully give the command \fB:e!\fR\ \fIname\fR\s-2CR\s0, which edits the other file discarding the changes you have made to the current file. To have the editor automatically save changes, include .I "set autowrite" in your EXINIT, and use \fB:n\fP instead of \fB:e\fP. .NH 2 Escaping to a shell .PP You can get to a shell to execute a single command by giving a .I vi command of the form \fB:!\fIcmd\fR\s-2CR\s0. The system will run the single command .I cmd and when the command finishes, the editor will ask you to hit a \s-2RETURN\s0 to continue. When you have finished looking at the output on the screen, you should hit \s-2RETURN\s0 and the editor will clear the screen and redraw it. You can then continue editing. You can also give another \fB:\fR command when it asks you for a \s-2RETURN\s0; in this case the screen will not be redrawn. .PP If you wish to execute more than one command in the shell, then you can give the command \fB:sh\fR\s-2CR\s0. This will give you a new shell, and when you finish with the shell, ending it by typing a \fB^D\fR, the editor will clear the screen and continue. .PP On systems which support it, \fB^Z\fP will suspend the editor and return to the (top level) shell. When the editor is resumed, the screen will be redrawn. .NH 2 Marking and returning .PP The command \fB\(ga\(ga\fR returned to the previous place after a motion of the cursor by a command such as \fB/\fR, \fB?\fR or \fBG\fR. You can also mark lines in the file with single letter tags and return to these marks later by naming the tags. Try marking the current line with the command \fBm\fR\fIx\fR, where you should pick some letter for \fIx\fR, say `a'. Then move the cursor to a different line (any way you like) and hit \fB\(gaa\fR. The cursor will return to the place which you marked. Marks last only until you edit another file. .PP When using operators such as .B d and referring to marked lines, it is often desirable to delete whole lines rather than deleting to the exact position in the line marked by \fBm\fR. In this case you can use the form \fB\(aa\fR\fIx\fR rather than \fB\(ga\fR\fIx\fR. Used without an operator, \fB\(aa\fR\fIx\fR will move to the first non-white character of the marked line; similarly \fB\(aa\(aa\fR moves to the first non-white character of the line containing the previous context mark \fB\(ga\(ga\fR. .NH 2 Adjusting the screen .PP If the screen image is messed up because of a transmission error to your terminal, or because some program other than the editor wrote output to your terminal, you can hit a \fB^L\fR, the \s-2ASCII\s0 form-feed character, to cause the screen to be refreshed. .PP On a dumb terminal, if there are @ lines in the middle of the screen as a result of line deletion, you may get rid of these lines by typing \fB^R\fR to cause the editor to retype the screen, closing up these holes. .PP Finally, if you wish to place a certain line on the screen at the top, middle, or bottom of the screen, you can position the cursor to that line, and then give a \fBz\fR command. You should follow the \fBz\fR command with a \s-2RETURN\s0 if you want the line to appear at the top of the window, a \fB.\fR if you want it at the center, or a \fB\-\fR if you want it at the bottom. .NH 1 Special topics .NH 2 Editing on slow terminals .PP When you are on a slow terminal, it is important to limit the amount of output which is generated to your screen so that you will not suffer long delays, waiting for the screen to be refreshed. We have already pointed out how the editor optimizes the updating of the screen during insertions on dumb terminals to limit the delays, and how the editor erases lines to @ when they are deleted on dumb terminals. .\" .PP .\" The use of the slow terminal insertion mode is controlled by the .\" .I slowopen .\" option. You can force the editor to use this mode even on faster terminals .\" by giving the command \fB:se slow\fR\s-2CR\s0. If your system is sluggish, .\" this helps lessen the amount of output coming to your terminal. .\" You can disable this option by \fB:se noslow\fR\s-2CR\s0. .\" .PP .\" The editor can simulate an intelligent terminal on a dumb one. Try .\" giving the command \fB:se redraw\fR\s-2CR\s0. This simulation generates .\" a great deal of output and is generally tolerable only on lightly loaded .\" systems and fast terminals. You can disable this by giving the command .\" \fB:se noredraw\fR\s-2CR\s0. .PP The editor also makes editing more pleasant at low speed by starting editing in a small window, and letting the window expand as you edit. This works particularly well on intelligent terminals. The editor can expand the window easily when you insert in the middle of the screen on these terminals. If possible, try the editor on an intelligent terminal to see how this works. .PP You can control the size of the window which is redrawn each time the screen is cleared by giving window sizes as argument to the commands which cause large screen motions: .DS .B ": / ? [[ ]] \(ga \(aa" .DE Thus if you are searching for a particular instance of a common string in a file, you can precede the first search command by a small number, say 3, and the editor will draw three line windows around each instance of the string which it locates. .PP You can easily expand or contract the window, placing the current line as you choose, by giving a number on a \fBz\fR command, after the \fBz\fR and before the following \s-2RETURN\s0, \fB.\fR or \fB\-\fR. Thus the command \fBz5.\fR redraws the screen with the current line in the center of a five line window.\*(dg .FS \*(dg Note that the command \fB5z.\fR has an entirely different effect, placing line 5 in the center of a new window. .FE .PP If the editor is redrawing or otherwise updating large portions of the display, you can interrupt this updating by hitting a \s-2DEL\s0 or \s-2RUB\s0 as usual. If you do this you may partially confuse the editor about what is displayed on the screen. You can still edit the text on the screen if you wish; clear up the confusion by hitting a \fB^L\fR; or move or search again, ignoring the current state of the display. .\" .PP .\" See section 7.8 on \fIopen\fR mode for another way to use the .\" .I vi .\" command set on slow terminals. .NH 2 Options, set, and editor startup files .PP The editor has a set of options, some of which have been mentioned above. The most useful options are given in the following table. .PP The options are of three kinds: numeric options, string options, and toggle options. You can set numeric and string options by a statement of the form .DS \fBset\fR \fIopt\fR\fB=\fR\fIval\fR .DE and toggle options can be set or unset by statements of one of the forms .DS \fBset\fR \fIopt\fR \fBset\fR \fBno\fR\fIopt\fR .DE .KF .TS lb lb lb lb l l l a. Name Default Description _ autoindent noai Supply indentation automatically autowrite noaw Auto write before \fB:n\fR, \fB:ta\fR, \fB^^\fR, \fB!\fR ignorecase noic Ignore case in searching list nolist Tabs print as ^I; end of lines $ magic magic . [ and * are special in scans number nonu Lines prefixed with line numbers paragraphs para=IPLPPPQPP LIpplpipbp Macros which start paragraphs ruler noruler Display a row/column/percentage ruler. sections sect=NHSHH HUnhsh Macros which start new sections shiftwidth sw=8 Shift distance for <, >, \fB^D\fP and \fB^T\fR showmatch nosm Show matching \fB(\fP or \fB{\fP term $TERM The kind of terminal being used .TE .KE These statements can be placed in your EXINIT in your environment, or given while you are running .I vi by preceding them with a \fB:\fR and following them with a \s-2CR\s0. .PP You can get a list of all options which you have changed by the command \fB:set\fR\s-2CR\s0, or the value of a single option by the command \fB:set\fR \fIopt\fR\fB?\fR\s-2CR\s0. A list of all possible options and their values is generated by \fB:set all\fP\s-2CR\s0. Set can be abbreviated \fBse\fP. Multiple options can be placed on one line, e.g. \fB:se ai aw nu\fP\s-2CR\s0. .PP Options set by the \fBset\fP command only last while you stay in the editor. It is common to want to have certain options set whenever you use the editor. This can be accomplished by creating a list of \fIex\fP commands\*(dg .FS \*(dg All commands which start with .B : are \fIex\fP commands. .FE which are to be run every time you start up \fIex\fP or \fIvi\fP. A typical list includes a \fBset\fP command, and possibly a few \fBmap\fP commands. Since it is advisable to get these commands on one line, they can be separated with the | character, for example: .DS \fBset\fP ai aw terse|\fBmap\fP @ dd|\fBmap\fP # x .DE which sets the options \fIautoindent\fP, \fIautowrite\fP, \fIterse\fP (the .B set command), makes @ delete a line (the first .B map ), and makes # delete a character (the second .B map ). (See section 6.9 for a description of the \fBmap\fP command.) This string should be placed in the variable EXINIT in your environment. If you use the shell \fIcsh\fP, put this line in the file .I .login in your home directory: .DS I setenv EXINIT 'set ai aw terse|map @ dd|map # x' .DE If you use the standard shell \fIsh\fP, put these lines in the file .I .profile in your home directory: .DS export EXINIT='set ai aw terse|map @ dd|map # x' .DE Of course, the particulars of the line would depend on which options you wanted to set. .NH 2 Recovering lost lines .PP You might have a serious problem if you delete a number of lines and then regret that they were deleted. Despair not, the editor saves the last 9 deleted blocks of text in a set of numbered registers 1\-9. You can get the \fIn\fR'th previous deleted text back in your file by the command "\fR\fIn\fR\|\fBp\fR. The "\fR here says that a buffer name is to follow, \fIn\fR is the number of the buffer you wish to try (use the number 1 for now), and .B p is the put command, which puts text in the buffer after the cursor. If this doesn't bring back the text you wanted, hit .B u to undo this and then \fB\&.\fR (period) to repeat the put command. In general the \fB\&.\fR command will repeat the last change you made. As a special case, when the last command refers to a numbered text buffer, the \fB.\fR command increments the number of the buffer before repeating the command. Thus a sequence of the form .DS \fB"1pu.u.u.\fR .DE will, if repeated long enough, show you all the deleted text which has been saved for you. You can omit the .B u commands here to gather up all this text in the buffer, or stop after any \fB\&.\fR command to keep just the then recovered text. The command .B P can also be used rather than .B p to put the recovered text before rather than after the cursor. .NH 2 Recovering lost files .PP If the system crashes, you can recover the work you were doing to within a few changes. You will normally receive mail when you next login giving you the name of the file which has been saved for you. You should then change to the directory where you were when the system crashed and give a command of the form: .DS % vi -r name .DE replacing \fIname\fR with the name of the file which you were editing. This will recover your work to a point near where you left off.\*(dg .FS \*(dg In rare cases, some of the lines of the file may be lost. The editor will give you the numbers of these lines and the text of the lines will be replaced by the string `LOST'. These lines will almost always be among the last few which you changed. You can either choose to discard the changes which you made (if they are easy to remake) or to replace the few lost lines by hand. .FE .PP You can get a listing of the files which are saved for you by giving the command: .DS I % vi -r .DE If there is more than one instance of a particular file saved, the editor gives you the newest instance each time you recover it. You can thus get an older saved copy back by first recovering the newer copies. .PP For this feature to work, .I vi must be correctly installed by a super user on your system, and the .I mail program must exist to receive mail. The invocation ``\fIvi -r\fP'' will not always list all saved files, but they can be recovered even if they are not listed. .NH 2 Continuous text input .PP When you are typing in large amounts of text it is convenient to have lines broken near the right margin automatically. You can cause this to happen by giving the command \fB:se wm=10\fR\s-2CR\s0. This causes all lines to be broken at a space at least 10 columns from the right hand edge of the screen. .PP If the editor breaks an input line and you wish to put it back together you can tell it to join the lines with \fBJ\fR. You can give \fBJ\fR a count of the number of lines to be joined as in \fB3J\fR to join 3 lines. The editor supplies whitespace, if appropriate, at the juncture of the joined lines, and leaves the cursor at this whitespace. You can kill the whitespace with \fBx\fR if you don't want it. .NH 2 Features for editing programs .PP The editor has a number of commands for editing programs. The thing that most distinguishes editing of programs from editing of text is the desirability of maintaining an indented structure to the body of the program. The editor has an .I autoindent facility for helping you generate correctly indented programs. .PP To enable this facility you can give the command \fB:se ai\fR\s-2CR\s0. Now try opening a new line with \fBo\fR and type some characters on the line after a few tabs. If you now start another line, notice that the editor supplies whitespace at the beginning of the line to line it up with the previous line. You cannot backspace over this indentation, but you can use \fB^D\fR key to backtab over the supplied indentation. .PP Each time you type \fB^D\fR you back up one position, normally to an 8 column boundary. This amount is settable; the editor has an option called .I shiftwidth which you can set to change this value. Try giving the command \fB:se sw=4\fR\s-2CR\s0 and then experimenting with autoindent again. .PP For shifting lines in the program left and right, there are operators .B < and .B >. These shift the lines you specify right or left by one .I shiftwidth . Try .B << and .B >> which shift one line left or right, and .B L shifting the rest of the display left and right. .PP If you have a complicated expression and wish to see how the parentheses match, put the cursor at a left or right parenthesis and hit \fB%\fR. This will show you the matching parenthesis. This works also for braces { and }, and brackets [ and ]. .PP If you are editing C programs, you can use the \fB[[\fR and \fB]]\fR keys to advance or retreat to a line starting with a \fB{\fR, i.e. a function declaration at a time. When \fB]]\fR is used with an operator it stops after a line which starts with \fB}\fR; this is sometimes useful with \fBy]]\fR. .NH 2 Filtering portions of the buffer .PP You can run system commands over portions of the buffer using the operator \fB!\fR. You can use this to sort lines in the buffer, or to reformat portions of the buffer with a pretty-printer. Try typing in a list of random words, one per line, and ending them with a blank line. Back up to the beginning of the list, and then give the command \fB!}sort\fR\s-2CR\s0. This says to sort the next paragraph of material, and the blank line ends a paragraph. .\" .NH 2 .\" Commands for editing \s-2LISP\s0 .\" .PP .\" If you are editing a \s-2LISP\s0 program you should set the option .\" .I lisp .\" by doing .\" \fB:se\ lisp\fR\s-2CR\s0. .\" This changes the \fB(\fR and \fB)\fR commands to move backward and forward .\" over s-expressions. .\" The \fB{\fR and \fB}\fR commands are like \fB(\fR and \fB)\fR but don't .\" stop at atoms. These can be used to skip to the next list, or through .\" a comment quickly. .\" .PP .\" The .\" .I autoindent .\" option works differently for \s-2LISP\s0, supplying indent to align at .\" the first argument to the last open list. If there is no such argument .\" then the indent is two spaces more than the last level. .\" .PP .\" There is another option which is useful for typing in \s-2LISP\s0, the .\" .I showmatch .\" option. .\" Try setting it with .\" \fB:se sm\fR\s-2CR\s0 .\" and then try typing a `(' some words and then a `)'. Notice that the .\" cursor shows the position of the `(' which matches the `)' briefly. .\" This happens only if the matching `(' is on the screen, and the cursor .\" stays there for at most one second. .\" .PP .\" The editor also has an operator to realign existing lines as though they .\" had been typed in with .\" .I lisp .\" and .\" .I autoindent .\" set. This is the \fB=\fR operator. .\" Try the command \fB=%\fR at the beginning of a function. This will realign .\" all the lines of the function declaration. .\" .PP .\" When you are editing \s-2LISP\s0,, the \fB[[\fR and \fR]]\fR advance .\" and retreat to lines beginning with a \fB(\fR, and are useful for dealing .\" with entire function definitions. .NH 2 Macros .PP .I Vi has a parameterless macro facility, which lets you set it up so that when you hit a single keystroke, the editor will act as though you had hit some longer sequence of keys. You can set this up if you find yourself typing the same sequence of commands repeatedly. .PP Briefly, there are two flavors of macros: .IP a) Ones where you put the macro body in a buffer register, say \fIx\fR. You can then type \fB@x\fR to invoke the macro. The \fB@\fR may be followed by another \fB@\fR to repeat the last macro. .IP b) You can use the .I map command from .I vi (typically in your .I EXINIT ) with a command of the form: .DS :map \fIlhs\fR \fIrhs\fR\s-2CR .DE mapping .I lhs into .I rhs. There are restrictions: .I lhs should be one keystroke (either 1 character or one function key) since it must be entered within one second (unless .I notimeout is set, in which case you can type it as slowly as you wish, and .I vi will wait for you to finish it before it echoes anything). The .I lhs can be no longer than 10 characters, the .I rhs no longer than 100. To get a space, tab or newline into .I lhs or .I rhs you should escape them with a \fB^V\fR. (It may be necessary to double the \fB^V\fR if the map command is given inside .I vi , rather than in .I ex .) Spaces and tabs inside the .I rhs need not be escaped. .PP Thus to make the \fBq\fR key write and exit the editor, you can give the command .DS I :map q :wq\fB^V^V\fP\s-2CR CR\s0 .DE which means that whenever you type \fBq\fR, it will be as though you had typed the four characters \fB:wq\fR\s-2CR\s0. A \fB^V\fR's is needed because without it the \s-2CR\s0 would end the \fB:\fR command, rather than becoming part of the .I map definition. There are two .B ^V 's because from within .I vi , two .B ^V 's must be typed to get one. The first \s-2CR\s0 is part of the .I rhs , the second terminates the : command. .PP Macros can be deleted with .DS I unmap lhs .DE .PP If the .I lhs of a macro is ``#0'' through ``#9'', this maps the particular function key instead of the 2 character ``#'' sequence. So that terminals without function keys can access such definitions, the form ``#x'' will mean function key .I x on all terminals (and need not be typed within one second). The character ``#'' can be changed by using a macro in the usual way: .DS :map \fB^V^V^I\fP # .DE to use tab, for example. (This won't affect the .I map command, which still uses .B #, but just the invocation from visual mode.) .PP The .I undo command reverses an entire macro call as a unit, if it made any changes. .PP Placing a `!' after the word .B map causes the mapping to apply to input mode, rather than command mode. Thus, to arrange for \fB^T\fP to be the same as 4 spaces in input mode, you can type: .DS :map \fB^T\fP \fB^V\fP\o'b/'\o'b/'\o'b/'\o'b/' .DE where .B \o'b/' is a blank. The \fB^V\fP is necessary to prevent the blanks from being taken as whitespace between the .I lhs and .I rhs . .NH Word Abbreviations .PP A feature similar to macros in input mode is word abbreviation. This allows you to type a short word and have it expanded into a longer word or words. The commands are .B :abbreviate and .B :unabbreviate (\fB:ab\fP and .B :una ) and have the same syntax as .B :map . For example: .DS :ab eecs Electrical Engineering and Computer Sciences .DE causes the word `eecs' to always be changed into the phrase `Electrical Engineering and Computer Sciences'. Word abbreviation is different from macros in that only whole words are affected. If `eecs' were typed as part of a larger word, it would be left alone. Also, the partial word is echoed as it is typed. There is no need for an abbreviation to be a single keystroke, as it should be with a macro. .NH 2 Abbreviations .PP The editor has a number of short commands which abbreviate longer commands which we have introduced here. You can find these commands easily on the quick reference card. They often save a bit of typing and you can learn them as convenient. .NH 1 Nitty-gritty details .NH 2 Line representation in the display .PP The editor folds long logical lines onto many physical lines in the display. Commands which advance lines advance logical lines and will skip over all the segments of a line in one motion. The command \fB|\fR moves the cursor to a specific column, and may be useful for getting near the middle of a long line to split it in half. Try \fB80|\fR on a line which is more than 80 columns long.\*(dg .FS \*(dg You can make long lines very easily by using \fBJ\fR to join together short lines. .FE .PP The editor only puts full lines on the display; if there is not enough room on the display to fit a logical line, the editor leaves the physical line empty, placing only an @ on the line as a place holder. When you delete lines on a dumb terminal, the editor will often just clear the lines to @ to save time (rather than rewriting the rest of the screen.) You can always maximize the information on the screen by giving the \fB^R\fR command. .PP If you wish, you can have the editor place line numbers before each line on the display. Give the command \fB:se nu\fR\s-2CR\s0 to enable this, and the command \fB:se nonu\fR\s-2CR\s0 to turn it off. You can have tabs represented as \fB^I\fR and the ends of lines indicated with `$' by giving the command \fB:se list\fR\s-2CR\s0; \fB:se nolist\fR\s-2CR\s0 turns this off. .PP Finally, lines consisting of only the character `~' are displayed when the last line in the file is in the middle of the screen. These represent physical lines which are past the logical end of file. .NH 2 Counts .PP Most .I vi commands will use a preceding count to affect their behavior in some way. The following table gives the common ways in which the counts are used: .DS .TS l lb. new window size : / ? [[ ]] \` \' scroll amount ^D ^U line/column number z G | repeat effect \fRmost of the rest\fP .TE .DE .PP The editor maintains a notion of the current default window size. On terminals which run at speeds greater than 1200 baud the editor uses the full terminal screen. On terminals which are slower than 1200 baud (most dialup lines are in this group) the editor uses 8 lines as the default window size. At 1200 baud the default is 16 lines. .PP This size is the size used when the editor clears and refills the screen after a search or other motion moves far from the edge of the current window. The commands which take a new window size as count all often cause the screen to be redrawn. If you anticipate this, but do not need as large a window as you are currently using, you may wish to change the screen size by specifying the new size before these commands. In any case, the number of lines used on the screen will expand if you move off the top with a \fB\-\fR or similar command or off the bottom with a command such as \s-2RETURN\s0 or \fB^D\fR. The window will revert to the last specified size the next time it is cleared and refilled.\*(dg .FS \*(dg But not by a \fB^L\fR which just redraws the screen as it is. .FE .PP The scroll commands \fB^D\fR and \fB^U\fR likewise remember the amount of scroll last specified, using half the basic window size initially. The simple insert commands use a count to specify a repetition of the inserted text. Thus \fB10a+\-\-\-\-\fR\s-2ESC\s0 will insert a grid-like string of text. A few commands also use a preceding count as a line or column number. .PP Except for a few commands which ignore any counts (such as \fB^R\fR), the rest of the editor commands use a count to indicate a simple repetition of their effect. Thus \fB5w\fR advances five words on the current line, while \fB5\fR\s-2RETURN\s0 advances five lines. A very useful instance of a count as a repetition is a count given to the \fB.\fR command, which repeats the last changing command. If you do \fBdw\fR and then \fB3.\fR, you will delete first one and then three words. You can then delete two more words with \fB2.\fR. .NH 2 More file manipulation commands .PP The following table lists the file manipulation commands which you can use when you are in .I vi . .KF .DS .TS lb l. :w write back changes :wq write and quit :x write (if necessary) and quit (same as ZZ) :e \fIname\fP edit file \fIname\fR :e! reedit, discarding changes :e + \fIname\fP edit, starting at end :e +\fIn\fP edit, starting at line \fIn\fP :e # edit alternate file :w \fIname\fP write file \fIname\fP :w! \fIname\fP overwrite file \fIname\fP :\fIx,y\fPw \fIname\fP write lines \fIx\fP through \fIy\fP to \fIname\fP :r \fIname\fP read file \fIname\fP into buffer :r !\fIcmd\fP read output of \fIcmd\fP into buffer :n edit next file in argument list :n! edit next file, discarding changes to current :n \fIargs\fP specify new argument list :ta \fItag\fP edit file containing tag \fItag\fP, at \fItag\fP .TE .DE .KE All of these commands are followed by a \s-2CR\s0 or \s-2ESC\s0. The most basic commands are \fB:w\fR and \fB:e\fR. A normal editing session on a single file will end with a \fBZZ\fR command. If you are editing for a long period of time you can give \fB:w\fR commands occasionally after major amounts of editing, and then finish with a \fBZZ\fR. When you edit more than one file, you can finish with one with a \fB:w\fR and start editing a new file by giving a \fB:e\fR command, or set .I autowrite and use \fB:n\fP . .PP If you make changes to the editor's copy of a file, but do not wish to write them back, then you must give an \fB!\fR after the command you would otherwise use; this forces the editor to discard any changes you have made. Use this carefully. .ne 1i .PP The \fB:e\fR command can be given a \fB+\fR argument to start at the end of the file, or a \fB+\fR\fIn\fR argument to start at line \fIn\fR\^. In actuality, \fIn\fR may be any editor command not containing a space, usefully a scan like \fB+/\fIpat\fR or \fB+?\fIpat\fR. In forming new names to the \fBe\fR command, you can use the character \fB%\fR which is replaced by the current file name, or the character \fB#\fR which is replaced by the alternate file name. The alternate file name is generally the last name you typed other than the current file. Thus if you try to do a \fB:e\fR and get a diagnostic that you haven't written the file, you can give a \fB:w\fR command and then a \fB:e #\fR command to redo the previous \fB:e\fR. .PP You can write part of the buffer to a file by finding out the lines that bound the range to be written using \fB^G\fR, and giving these numbers after the \fB:\fR and before the \fBw\fP, separated by \fB,\fR's. You can also mark these lines with \fBm\fR and then use an address of the form \fB\(aa\fR\fIx\fR\fB,\fB\(aa\fR\fIy\fR on the \fBw\fR command here. .PP You can read another file into the buffer after the current line by using the \fB:r\fR command. You can similarly read in the output from a command, just use \fB!\fR\fIcmd\fR instead of a file name. .PP If you wish to edit a set of files in succession, you can give all the names on the command line, and then edit each one in turn using the command \fB:n\fR. It is also possible to respecify the list of files to be edited by giving the \fB:n\fR command a list of file names, or a pattern to be expanded as you would have given it on the initial .I vi command. .PP If you are editing large programs, you will find the \fB:ta\fR command very useful. It utilizes a data base of function names and their locations, which can be created by programs such as .I ctags , to quickly find a function whose name you give. If the \fB:ta\fR command requires the editor to switch files, then you must \fB:w\fR or abandon any changes before switching. You can repeat the \fB:ta\fR command without any arguments to look for the same tag again. .NH 2 More about searching for strings .PP When you are searching for strings in the file with \fB/\fR and \fB?\fR, the editor normally places you at the next or previous occurrence of the string. If you are using an operator such as \fBd\fR, \fBc\fR or \fBy\fR, then you may well wish to affect lines up to the line before the line containing the pattern. You can give a search of the form \fB/\fR\fIpat\fR\fB/\-\fR\fIn\fR to refer to the \fIn\fR'th line before the next line containing \fIpat\fR, or you can use \fB+\fR instead of \fB\-\fR to refer to the lines after the one containing \fIpat\fR. If you don't give a line offset, then the editor will affect characters up to the match place, rather than whole lines; thus use ``+0'' to affect to the line which matches. .PP You can have the editor ignore the case of words in the searches it does by giving the command \fB:se ic\fR\s-2CR\s0. The command \fB:se noic\fR\s-2CR\s0 turns this off. .ne 1i .PP Strings given to searches may actually be regular expressions. If you do not want or need this facility, you should .DS set nomagic .DE in your EXINIT. In this case, only the characters \fB^\fR and \fB$\fR are special in patterns. The character \fB\e\fR is also then special (as it is most everywhere in the system), and may be used to get at the an extended pattern matching facility. It is also necessary to use a \e before a \fB/\fR in a forward scan or a \fB?\fR in a backward scan, in any case. The following table gives the extended forms when \fBmagic\fR is set. .DS .TS lb l. ^ at beginning of pattern, matches beginning of line $ at end of pattern, matches end of line \fB\&.\fR matches any character \e< matches the beginning of a word \e> matches the end of a word [\fIstr\fP] matches any single character in \fIstr\fP [^\fIstr\fP] matches any single character not in \fIstr\fP [\fIx\fP\-\fIy\fP] matches any character between \fIx\fP and \fIy\fP * matches any number of the preceding pattern .TE .DE If you use \fBnomagic\fR mode, then the \fB. [\fR and \fB*\fR primitives are given with a preceding \e. .NH 2 More about input mode .PP There are a number of characters which you can use to make corrections during input mode. These are summarized in the following table. .sp .5 .DS .TS lb l. ^H deletes the last input character ^W deletes the last input word, defined as by \fBb\fR erase your erase character, same as \fB^H\fP kill your kill character, deletes the input on this line \e escapes a following \fB^H\fP and your erase and kill \s-2ESC\s0 ends an insertion \s-2DEL\s0 interrupts an insertion, terminating it abnormally \s-2CR\s0 starts a new line ^D backtabs over \fIautoindent\fP 0^D kills all the \fIautoindent\fP ^^D same as \fB0^D\fP, but restores indent next line ^V quotes the next non-printing character into the file .TE .DE .sp .5 .PP The most usual way of making corrections to input is by typing \fB^H\fR to correct a single character, or by typing one or more \fB^W\fR's to back over incorrect words. If you use \fB#\fR as your erase character in the normal system, it will work like \fB^H\fR. .PP Your system kill character, normally \fB@\fR, \fB^X\fP or \fB^U\fR, will erase all the input you have given on the current line. In general, you can neither erase input back around a line boundary nor can you erase characters which you did not insert with this insertion command. To make corrections on the previous line after a new line has been started you can hit \s-2ESC\s0 to end the insertion, move over and make the correction, and then return to where you were to continue. The command \fBA\fR which appends at the end of the current line is often useful for continuing. .PP If you wish to type in your erase or kill character (say # or @) then you must precede it with a \fB\e\fR, just as you would do at the normal system command level. A more general way of typing non-printing characters into the file is to precede them with a \fB^V\fR. The \fB^V\fR echoes as a \fB^\fR character on which the cursor rests. This indicates that the editor expects you to type a control character. In fact you may type any character and it will be inserted into the file at that point.* .FS * This is not quite true. The implementation of the editor does not allow the \s-2NULL\s0 (\fB^@\fR) character to appear in files. Also the \s-2LF\s0 (linefeed or \fB^J\fR) character is used by the editor to separate lines in the file, so it cannot appear in the middle of a line. You can insert any other character, however, if you wait for the editor to echo the \fB^\fR before you type the character. In fact, the editor will treat a following letter as a request for the corresponding control character. This is the only way to type \fB^S\fR or \fB^Q\fP, since the system normally uses them to suspend and resume output and never gives them to the editor to process. .FE .PP If you are using \fIautoindent\fR you can backtab over the indent which it supplies by typing a \fB^D\fR. This backs up to a \fIshiftwidth\fR boundary. This only works immediately after the supplied \fIautoindent\fR. .PP When you are using \fIautoindent\fR you may wish to place a label at the left margin of a line. The way to do this easily is to type \fB^\fR and then \fB^D\fR. The editor will move the cursor to the left margin for one line, and restore the previous indent on the next. You can also type a \fB0\fR followed immediately by a \fB^D\fR if you wish to kill all the indent and not have it come back on the next line. .NH 2 Upper case only terminals .PP If your terminal has only upper case, you can still use .I vi by using the normal system convention for typing on such a terminal. Characters which you normally type are converted to lower case, and you can type upper case letters by preceding them with a \e. The characters { ~ } | \(ga are not available on such terminals, but you can escape them as \e( \e^ \e) \e! \e\(aa. These characters are represented on the display in the same way they are typed.\*(dd .FS \*(dd The \e character you give will not echo until you type another key. .FE .NH 2 Vi and ex .PP .I Vi is actually one mode of editing within the editor .I ex . When you are running .I vi you can escape to the line oriented editor of .I ex by giving the command \fBQ\fR. All of the .B : commands which were introduced above are available in .I ex. Likewise, most .I ex commands can be invoked from .I vi using \fB:\fR. Just give them without the \fB:\fR and follow them with a \s-2CR\s0. .PP In rare instances, an internal error may occur in .I vi . In this case you will get a diagnostic and be left in the command mode of .I ex . You can then save your work and quit if you wish by giving a command \fBx\fR after the \fB:\fR which \fIex\fR prompts you with, or you can reenter \fIvi\fR by giving .I ex a .I vi command. .PP There are a number of things which you can do more easily in .I ex than in .I vi. Systematic changes in line oriented material are particularly easy. You can read the advanced editing documents for the editor .I ed to find out a lot more about this style of editing. Experienced users often mix their use of .I ex command mode and .I vi command mode to speed the work they are doing. .\" .NH 2 .\" Open mode: vi on hardcopy terminals and ``glass tty's'' .\" \(dd .\" .PP .\" If you are on a hardcopy terminal or a terminal which does not have a cursor .\" which can move off the bottom line, you can still use the command set of .\" .I vi, .\" but in a different mode. .\" When you give a .\" .I vi .\" command, the editor will tell you that it is using .\" .I open .\" mode. .\" This name comes from the .\" .I open .\" command in .\" .I ex, .\" which is used to get into the same mode. .\" .PP .\" The only difference between .\" .I visual .\" mode .\" and .\" .I open .\" mode is the way in which the text is displayed. .\" .PP .\" In .\" .I open .\" mode the editor uses a single line window into the file, and moving backward .\" and forward in the file causes new lines to be displayed, always below the .\" current line. .\" Two commands of .\" .I vi .\" work differently in .\" .I open: .\" .B z .\" and .\" \fB^R\fR. .\" The .\" .B z .\" command does not take parameters, but rather draws a window of context around .\" the current line and then returns you to the current line. .\" .PP .\" If you are on a hardcopy terminal, .\" the .\" .B ^R .\" command will retype the current line. .\" On such terminals, the editor normally uses two lines to represent the .\" current line. .\" The first line is a copy of the line as you started to edit it, and you work .\" on the line below this line. .\" When you delete characters, the editor types a number of \e's to show .\" you the characters which are deleted. The editor also reprints the current .\" line soon after such changes so that you can see what the line looks .\" like again. .\" .PP .\" It is sometimes useful to use this mode on very slow terminals which .\" can support .\" .I vi .\" in the full screen mode. .\" You can do this by entering .\" .I ex .\" and using an .\" .I open .\" command. .\" .LP .SH Acknowledgements .PP Bruce Englar encouraged the early development of this display editor. Peter Kessler helped bring sanity to version 2's command layout. Bill Joy wrote versions 1 and 2.0 through 2.7, and created the framework that users see in the present editor. Mark Horton added macros and other features and made the editor work on a large number of terminals and Unix systems. ================================================ FILE: docs/USD.doc/vitut/vi.summary ================================================ .\" $OpenBSD: vi.summary,v 1.6 2004/01/24 12:29:13 jmc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1980, 1993 .\" The Regents of the University of California. All rights reserved. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)vi.summary 8.3 (Berkeley) 8/18/96 .\" .ds CH .ds CF .de TS .br .if !\\n(1T .RT .ul 0 .ti \\n(.iu .if t .sp 0.25 .if n .sp .if \\$1H .TQ .nr IX 1 .. .nr PS 9 .ps 9 .nr VS 11 .vs 11 .nr HM .50i .nr FM .25i .nr PO 1.0i .po 1.0i .nr LL 4.5i .ll 4.5i .de nc .bp .. .de h .LG .B \\$1 .R .NL .. .LG .LG .B .ce Ex Quick Reference .R .NL .LP .LP .h "Entering/leaving ex" .TS aw(1.4i)b aw(1.8i). % ex \fIname\fP edit \fIname\fP, start at end % ex +\fIn\fP \fIname\fP ... at line \fIn\fP % ex \-t \fItag\fP start at \fItag\fP % ex \-r list saved files % ex \-r \fIname\fP recover file \fIname\fP % ex \fIname\fP ... edit first; rest via \fB:n\fP % ex \-R \fIname\fP read only mode : x exit, saving changes : q! exit, discarding changes .TE .h "Ex states" .TS lw(1i) lw(2.0i). Command T{ Normal and initial state. Input prompted for by \fB:\fP. Your kill character cancels partial command. T} Insert T{ Entered by \fBa\fP \fBi\fP and \fBc\fP. Arbitrary text then terminates with line having only \fB.\fP character on it or abnormally with interrupt. T} Open/visual T{ Entered by \fBopen\fP or \fBvi\fP, terminates with \fBQ\fP or ^\e. T} .TE .h "Ex commands" .TS lw(.45i) lw(.08i)b lw(.45i) lw(.08i)b lw(.45i) lw(.08i)b. abbrev ab next n unabbrev una append a number nu undo u args ar open o unmap unm change c preserve pre version ve copy co print p visual vi delete d put pu write w edit e quit q xit x file f read re yank ya global g recover rec \fIwindow\fP z insert i rewind rew \fIescape\fP ! join j set se \fIlshift\fP < list l shell sh \fIprint next\fP \fRCR\fP map source so \fIresubst\fP & mark ma stop st \fIrshift\fP > move m substitute s \fIscroll\fP ^D .TE .h "Ex command addresses" .TS lw(.3i)b lw(0.8i) lw(.3i)b lw(0.8i). \fIn\fP line \fIn\fP /\fIpat\fP next with \fIpat\fP \&. current ?\fIpat\fP previous with \fIpat\fP $ last \fIx\fP-\fIn\fP \fIn\fP before \fIx\fP + next \fIx\fP,\fIy\fP \fIx\fP through \fIy\fP \- previous \(aa\fIx\fP marked with \fIx\fP +\fIn\fP \fIn\fP forward \(aa\(aa previous context % 1,$ .TE .nc .h "Specifying terminal type" .TS aw(1.7i)b aw(1.5i). % setenv TERM \fItype\fP \fIcsh\fP and all version 6 $ export TERM=\fItype\fP \fIsh\fP in Version 7 \fRSee also\fP \fItset\fR(1) .TE .h "Some terminal types" .TS lw(.4i) lw(.4i) lw(.4i) lw(.4i) lw(.4i). 2621 43 adm31 dw1 h19 2645 733 adm3a dw2 i100 300s 745 c100 gt40 mime 33 act4 dm1520 gt42 owl 37 act5 dm2500 h1500 t1061 4014 adm3 dm3025 h1510 vt52 .TE .h "Initializing options" .TS lw(.9i)b aw(1.5i). EXINIT place \fBset\fP's here in environment var set \fIx\fP enable option set no\fIx\fP disable option set \fIx\fP=\fIval\fP give value \fIval\fP set show changed options set all show all options set \fIx\fP? show value of option \fIx\fP .TE .h "Useful options" .TS lw(.9i)b lw(.3i) lw(1.0i). autoindent ai supply indent autowrite aw write before changing files ignorecase ic in scanning .\" lisp \fB( ) { }\fP are s-exp's list print ^I for tab, $ at end magic \fB. [ *\fP special in patterns number nu number lines paragraphs para macro names which start ... redraw simulate smart terminal scroll command mode lines sections sect macro names ... shiftwidth sw for \fB< >\fP, and input \fB^D\fP showmatch sm to \fB)\fP and \fB}\fP as typed .\" slowopen slow choke updates during insert window visual mode lines wrapscan ws around end of buffer? wrapmargin wm automatic line splitting .TE .LP .h "Scanning pattern formation" .TS aw(.9i)b aw(1.0i). ^ beginning of line $ end of line \fB.\fR any character \e< beginning of word \e> end of word [\fIstr\fP] any char in \fIstr\fP [^\fIstr\fP] ... not in \fIstr\fP [\fIx\-y\fP] ... between \fIx\fP and \fIy\fP * any number of preceding .TE .nc .LP .LG .LG .B .ce Vi Quick Reference .NL .R .LP .LP .h "Entering/leaving vi" .TS aw(1.4i)b aw(1.8i). % vi \fIname\fP edit \fIname\fP at top % vi +\fIn\fP \fIname\fP ... at line \fIn\fP % vi + \fIname\fP ... at end % vi \-r list saved files % vi \-r \fIname\fP recover file \fIname\fP % vi \fIname\fP ... edit first; rest via \fB:n\fP % vi \-t \fItag\fP start at \fItag\fP % vi +/\fIpat\fP \fIname\fP search for \fIpat\fP % view \fIname\fP read only mode ZZ exit from vi, saving changes ^Z stop vi for later resumption .TE .h "The display" .TS lw(.75i) lw(2.2i). Last line T{ Error messages, echoing input to \fB: / ?\fP and \fB!\fR, feedback about i/o and large changes. T} @ lines On screen only, not in file. ~ lines Lines past end of file. ^\fIx\fP Control characters, ^? is delete. tabs Expand to spaces, cursor at last. .TE .LP .h "Vi states" .TS lw(.75i) lw(2.2i). Command T{ Normal and initial state. Others return here. ESC (escape) cancels partial command. T} Insert T{ Entered by \fBa i A I o O c C s S\fP \fBR\fP. Arbitrary text then terminates with ESC character, or abnormally with interrupt. T} Last line T{ Reading input for \fB: / ?\fP or \fB!\fP; terminate with ESC or CR to execute, interrupt to cancel. T} .TE .h "Counts before vi commands" .TS lw(1.5i) lw(1.7i)b. line/column number z G | scroll amount ^D ^U replicate insert a i A I repeat effect \fRmost rest\fP .TE .h "Simple commands" .TS lw(1.5i)b lw(1.7i). dw delete a word de ... leaving punctuation dd delete a line 3dd ... 3 lines i\fItext\fP\fRESC\fP insert text \fIabc\fP cw\fInew\fP\fRESC\fP change word to \fInew\fP ea\fIs\fP\fRESC\fP pluralize word xp transpose characters .TE .nc .h "Interrupting, cancelling" .TS aw(0.75i)b aw(1.6i). ESC end insert or incomplete cmd ^? (delete or rubout) interrupts ^L reprint screen if \fB^?\fR scrambles it .TE .h "File manipulation" .TS aw(0.75i)b aw(1.6i). :w write back changes :wq write and quit :q quit :q! quit, discard changes :e \fIname\fP edit file \fIname\fP :e! reedit, discard changes :e + \fIname\fP edit, starting at end :e +\fIn\fR edit starting at line \fIn\fR :e # edit alternate file ^^ synonym for \fB:e #\fP :w \fIname\fP write file \fIname\fP :w! \fIname\fP overwrite file \fIname\fP :sh run shell, then return :!\fIcmd\fP run \fIcmd\fR, then return :n edit next file in arglist :n \fIargs\fP specify new arglist :f show current file and line ^G synonym for \fB:f\fP :ta \fItag\fP to tag file entry \fItag\fP ^] \fB:ta\fP, following word is \fItag\fP .TE .h "Positioning within file" .TS aw(0.75i)b aw(1.6i). ^F forward screenfull ^B backward screenfull ^D scroll down half screen ^U scroll up half screen G goto line (end default) /\fIpat\fR next line matching \fIpat\fR ?\fIpat\fR prev line matching \fIpat\fR n repeat last \fB/\fR or \fB?\fR N reverse last \fB/\fR or \fB?\fR /\fIpat\fP/+\fIn\fP n'th line after \fIpat\fR ?\fIpat\fP?\-\fIn\fP n'th line before \fIpat\fR ]] next section/function [[ previous section/function % find matching \fB( ) {\fP or \fB}\fP .TE .h "Adjusting the screen" .TS aw(0.75i)b aw(1.6i). ^L clear and redraw ^R retype, eliminate @ lines z\fRCR\fP redraw, current at window top z\- ... at bottom z\|. ... at center /\fIpat\fP/z\- \fIpat\fP line at bottom z\fIn\fP\|. use \fIn\fP line window ^E scroll window down 1 line ^Y scroll window up 1 line .TE .nc .h "Marking and returning .TS aw(0.5i)b aw(2.0i). \(ga\(ga previous context \(aa\(aa ... at first non-white in line m\fIx\fP mark position with letter \fIx\fP \(ga\fIx\fP to mark \fIx\fP \(aa\fIx\fP ... at first non-white in line .TE .h "Line positioning" .TS aw(0.5i)b aw(2.0i). H home window line L last window line M middle window line + next line, at first non-white \- previous line, at first non-white \fRCR\fP return, same as + ^J \fRor\fP j next line, same column ^ \fRor\fP k previous line, same column .TE .h "Character positioning" .TS aw(0.5i)b aw(2.0i). ^ first non white 0 beginning of line $ end of line h \fRor\fP \(-> forward l \fRor\fP \(<- backwards ^H same as \fB\(<-\fP \fRspace\fP same as \fB\(->\fP f\fIx\fP find \fIx\fP forward F\fIx\fP \fBf\fR backward t\fIx\fP upto \fIx\fP forward T\fIx\fP back upto \fIx\fP ; repeat last \fBf F t\fP or \fBT\fP , inverse of \fB;\fP | to specified column % find matching \fB( { )\fP or \fB}\fR .TE .h "Words, sentences, paragraphs" .TS aw(0.5i)b aw(2.0i). w word forward b back word e end of word ) to next sentence } to next paragraph ( back sentence { back paragraph W blank delimited word B back \fBW\fP E to end of \fBW\fP .TE .h "Commands for \s-2LISP\s0" .TS aw(0.5i)b aw(2.0i). ) Forward s-expression } ... but don't stop at atoms ( Back s-expression { ... but don't stop at atoms .TE .nc .h "Corrections during insert" .TS aw(.5i)b aw(2.0i). ^H erase last character ^W erases last word \fRerase\fP your erase, same as \fB^H\fP \fRkill\fP your kill, erase input this line \e escapes \fB^H\fR, your erase and kill \fRESC\fP ends insertion, back to command ^? interrupt, terminates insert ^D backtab over \fIautoindent\fP ^^D kill \fIautoindent\fP, save for next 0^D ... but at margin next also ^V quote non-printing character .TE .h "Insert and replace" .TS aw(.5i)b aw(2.0i). a append after cursor i insert before A append at end of line I insert before first non-blank o open line below O open above r\fIx\fP replace single char with \fIx\fP R replace characters .TE .h "Operators (double to affect lines)" .TS aw(0.5i)b aw(2.0i). d delete c change < left shift > right shift ! filter through command \&= indent for \s-2LISP\s0 y yank lines to buffer .TE .h "Miscellaneous operations" .TS aw(0.5i)b aw(2.0i). C change rest of line D delete rest of line s substitute chars S substitute lines J join lines x delete characters X ... before cursor Y yank lines .TE .h "Yank and put" .TS aw(0.5i)b aw(2.0i). p put back lines P put before "\fIx\fPp put from buffer \fIx\fP "\fIx\fPy yank to buffer \fIx\fP "\fIx\fPd delete into buffer \fIx\fP .TE .h "Undo, redo, retrieve" .TS aw(0.5i)b aw(2.0i). u undo last change U restore current line \fB.\fP repeat last change "\fId\fP\|p retrieve \fId\fP'th last delete .TE ================================================ FILE: docs/ev ================================================ Ev: Vi: Result: (Cursor keys). Move around the file. Meta key commands: ^A<#> <#>G Goto line #. ^A$ G Goto the end of the file. ^A/ / Prompt and execute a forward search. ^A: : Prompt and execute an ex command. ^A? ? Prompt and execute a backward search. ^Ac y' Copy to mark in line mode (or copy the current line). ^AC y` Copy to mark in character mode. ^Ad d' Delete to mark in line mode (or delete the current line). ^AD d` Delete to mark in character mode. ^Aj J Join lines. ^Am m Mark the current cursor position. ^AN N Repeat search in the reverse direction. ^An ^A Search for the word under the cursor. ^Ar u Redo a command. ^Au u Undo a command. Single key commands: ^B ^B Page up a screen. ^C ^C Interrupt long-running commands. ^D ^D Page down a half-screen. ^E $ End of line. ^F ^F Page down a screen. ^G ^G File status/information. ^H X Delete the character to the left of the cursor. ^I (TAB) ^J j Cursor down one line. ^K k Cursor up one line. ^L ^L Redraw the screen. ^M (CR) ^M In insert mode, split the line at the current cursor, creating a new line. In overwrite mode, cursor down one line. ^N n Repeat previous search, in previous direction. ^O (UNUSED) ^P p Paste the cut text at the cursor position. ^Q (XON/XOFF) ^R (UNUSED) ^S (XON/XOFF) ^T D Truncate the line at the cursor position. ^U ^U Page up a half-screen. ^V ^V Insert/overwrite with a literal next character. ^W w Move forward one whitespace separated word. ^X x Delete the current character. ^Y (UNUSED) ^Z ^Z Suspend. New ex mode commands: ^A:set ov[erwrite] Toggle "insert" mode, so that input keys overwrite the existing characters. ================================================ FILE: docs/ev.license ================================================ SPDX-License-Identifier: BSD-3-Clause Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/help ================================================ MOVING THE CURSOR: k - cursor up ^F - page forward / - search forward j - cursor down ^B - page backward ? - search backward h - cursor left w - move forward a "word" n - repeat the last search l - cursor right b - move backward a "word" ENTERING TEXT: a - append after the cursor. Use the key to return to i - insert before the cursor. command mode. o - open a new line below the cursor. O - open new line above the cursor. WRITING AND EXITING: :w - write the file :q - exit the file :q! - exit without writing the file :# - move to a line (e.g., :35 moves to line 35) MISCELLANEOUS: ^G - display the file name J - join two lines (use i to split a line) u - undo the last change (enter . after a 'u' to undo more than one change) =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= VI COMMANDS: ^A search forward for cursor word ^B scroll up by screens ^C interrupt an operation (e.g. read, write, search) ^D scroll down by half screens (setting count) ^E scroll down by lines ^F scroll down by screens ^G file status ^H move left by characters ^J move down by lines ^L redraw screen ^M move down by lines (to first non-blank) ^N move down by lines ^P move up by lines ^R redraw screen ^T tag pop ^U half page up (set count) ^V input a literal character ^W move to next screen ^Y page up by lines ^Z suspend editor ^[ exit input mode, cancel partial commands ^\ switch to ex mode ^] tag push cursor word ^^ switch to previous file move right by columns ! filter through command(s) to motion # number increment/decrement $ move to last column % move to match & repeat substitution ' move to mark (to first non-blank) ( move back sentence ) move forward sentence + move down by lines (to first non-blank) , reverse last F, f, T or t search - move up by lines (to first non-blank) . repeat the last command / search forward 0 move to first character : ex command ; repeat last F, f, T or t search < shift lines left to motion > shift lines right to motion ? search backward @ execute buffer A append to the line B move back bigword C change to end-of-line D delete to end-of-line E move to end of bigword F character in line backward search G move to line H move to count lines from screen top I insert before first nonblank J join lines L move to screen bottom M move to screen middle N reverse last search O insert above line P insert before cursor from buffer Q switch to ex mode R replace characters S substitute for the line(s) T before character in line backward search U Restore the current line W move to next bigword X delete character before cursor Y copy line ZZ save file and exit [[ move back section ]] move forward section ^ move to first non-blank _ move to first non-blank ` move to mark a append after cursor b move back word c change to motion d delete to motion e move to end of word f character in line forward search h move left by columns i insert before cursor j move down by lines k move up by lines l move right by columns m set mark n repeat last search o append after line p insert after cursor from buffer r replace character s substitute character t before character in line forward search u undo last change w move to next word x delete character y copy text to motion into a cut buffer z re-position the screen { move back paragraph | move to column } move forward paragraph ~ reverse case =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= EX COMMANDS: ^D: scroll lines !: filter lines through commands or run commands #: display numbered lines &: repeat the last substitution *: execute a buffer <: shift lines left =: display line number >: shift lines right @: execute a buffer append: append input to a line abbreviate: specify an input abbreviation args: display file argument list bg: background the current screen change: change lines to input cd: change the current directory chdir: change the current directory copy: copy lines elsewhere in the file delete: delete lines from the file display: display buffers, screens or tags [Ee]dit: begin editing another file [Ee]x: begin editing another file exusage: display ex command usage statement file: display (and optionally set) file name fg: switch the current screen and a backgrounded screen global: execute a global command on lines matching an RE help: display help statement insert: insert input before a line join: join lines into a single line k: mark a line position list: display lines in an unambiguous form move: move lines elsewhere in the file mark: mark a line position map: map input or commands to one or more keys mkexrc: write a .exrc file [Nn]ext: edit (and optionally specify) the next file number: change display to number lines open: enter "open" mode (not implemented) print: display lines preserve: preserve an edit session for recovery [Pp]revious: edit the previous file in the file argument list put: append a cut buffer to the line quit: exit ex/vi read: append input from a command or file to the line recover: recover a saved file resize: grow or shrink the current screen rewind: re-edit all the files in the file argument list s: substitute on lines matching an RE script: run a shell in a screen set: set options (use ":set all" to see all options) shell: suspend editing and run a shell source: read a file of ex commands stop: suspend the edit session suspend: suspend the edit session t: copy lines elsewhere in the file [Tt]ag: edit the file containing the tag tagnext: move to the next tag tagpop: return to the previous group of tags tagprev: move to the previous tag tagtop: discard all tags undo: undo the most recent change unabbreviate: delete an abbreviation unmap: delete an input or command map v: execute a global command on lines NOT matching an RE version: display the program version information visual: enter visual (vi) mode from ex mode [Vv]isual: edit another file (from vi mode only) viusage: display vi key usage statement write: write the file wn: write the file and switch to the next file wq: write the file and exit xit: exit yank: copy lines to a cut buffer z: display different screens of the file ~: replace previous RE with previous replacement string, =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Edit options: noaltwerase noerrorbells nolist remap noterse noautoindent noexpandtab lock report=5 notildeop autoprint noexrc magic noruler timeout noautowrite noextended matchtime=7 scroll=21 nottywerase backup="" filec=" " mesg nosearchincr noverbose nobeautify noflash noprint="" nosecure novisibletab nobserase hardtabs=0 nonumber shiftwidth=8 warn cdpath=":" noiclower nooctal noshowmatch window=42 cedit="" noignorecase open noshowmode nowindowname columns=86 noimctrl path="" sidescroll=16 wraplen=0 nocomment keytime=6 print="" tabstop=8 wrapmargin=0 noedcompatible noleftright prompt taglength=0 wrapscan escapetime=2 lines=43 noreadonly tags="tags" nowriteany directory="/tmp" imkey="/?aioAIO" paragraphs="iplpppqpp lipplpipbp" recdir="/var/tmp/vi.recover" sections="NHSHH HUnhsh" shell="/bin/sh" shellmeta="~{[*?$`'"\" term="vt100" ================================================ FILE: docs/help.license ================================================ SPDX-License-Identifier: BSD-3-Clause Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/autowrite ================================================ Vi autowrite behavior, the fields with *'s are "don't cares". =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Commands that are affected only by autowrite: Command File Autowrite? Action: modified? ----------------------------------------------- ^Z Y Y Write file and suspend. ^Z Y N Suspend. ^Z N * Suspend. # This behavior is NOT identical to :edit. ^^ Y Y Write file and jump. ^^ Y N Error. ^^ N * Jump. # The new nvi command ^T (:tagpop) behaves identically to ^]. # This behavior is identical to :tag, :tagpop, and :tagpush with # force always set to N. ^] Y Y Write file and jump. ^] Y N Error. ^] N * Jump. # There's no way to specify a force flag to the '!' command. :! Y Y Write file and execute. :! Y N Warn (if warn option) and execute. :! N * Execute. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Commands that are affected by both autowrite and force: NOTE: the "force" flag is never passed on, i.e. the write to the file caused by the autowrite flag is never forced. Command File Autowrite? Force? Action: modified? (!) ------------------------------------------------------- # The first rule (YYY) is historic practice, but seems wrong. # In nvi, :next and :prev commands behave identically to :rewind. :next Y Y Y Write changes and jump. :next Y Y N Write changes and jump. :next Y N Y Abandon changes and jump. :next Y N N Error. :next N * * Jump. :rewind Y Y Y Abandon changes and jump. :rewind Y Y N Write changes and jump. :rewind Y N Y Abandon changes and jump. :rewind Y N N Error. :rewind N * * Jump. # The new nvi commands, :tagpop and :tagtop, behave identically to :tag. # Note, this behavior is the same as :rewind and friends, as well. :tag Y Y Y Abandon changes and jump. :tag Y Y N Write changes and jump. :tag Y N Y Abandon changes and jump. :tag Y N N Error. :tag N * * Jump. # The command :suspend behaves identically to :stop. :stop Y Y Y Suspend. :stop Y Y N Write changes and suspend. :stop Y N Y Suspend. :stop Y N N Suspend. :stop N * * Suspend. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Commands that might be affected by autowrite, but aren't: Command File Autowrite? Force? Action: modified? (!) ------------------------------------------------------- #:ex, and :vi (executed while in vi mode) behave identically to :edit. :edit Y * Y Abandon changes and jump. :edit Y * N Error. :edit N * * Jump. :quit Y * Y Quit. :quit Y * N Error. :quit N * * Quit. :shell * * * Execute shell. :xit Y * * Write changes and exit. :xit N * * Exit. ================================================ FILE: docs/internals/autowrite.license ================================================ # $OpenBSD: autowrite,v 1.3 2001/01/29 01:58:37 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # @(#)autowrite 8.3 (Berkeley) 2/17/95 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/context ================================================ In historic vi, the previous context mark was always set: ex address: any number, , , , , ex commands: undo, "z.", global, v vi commands: (, ), {, }, %, [[, ]], ^] nvi adds the vi command ^T to this list. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= In historic vi, the previous context mark was set if the line changed: vi commands: ', G, H, L, M, z =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= In historic vi, the previous context mark was set if the line or column changed: vi commands: `, /, ?, N, n nvi adds the vi command ^A to this list. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= In historic vi, the previous context mark was set in non-visual mode for ^R and ^L if the line changed, but I have yet to figure out how the line could change. ================================================ FILE: docs/internals/context.license ================================================ # $OpenBSD: context,v 1.3 2001/01/29 01:58:37 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # @(#)context 8.6 (Berkeley) 10/14/94 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/gdb.script ================================================ # $OpenBSD: gdb.script,v 1.3 2001/01/29 01:58:38 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 1996, 1997, 1998, 1999, 2000 Keith Bostic # Copyright (c) 2021-2024 Jeffrey H. Johnson # @(#)gdb.script 8.5 (Berkeley) 5/4/96 # display the VI screen map # usage dmap(sp) define dmap set $h = ((VI_PRIVATE *)$arg0->vi_private)->h_smap set $t = ((VI_PRIVATE *)$arg0->vi_private)->t_smap while ($h <= $t) printf "lno: %2d; soff %d coff %d ", \ (int)$h->lno, (int)$h->soff, (int)$h->coff if ($h->c_ecsize == 0) printf "flushed\n" else printf "\n\tsboff %d; scoff %d\n", \ (int)$h->c_sboff, (int)$h->c_scoff printf "\teboff %d; eclen %d; ecsize %d\n", \ (int)$h->c_eboff, (int)$h->c_eclen, \ (int)$h->c_ecsize end set $h = $h + 1 end end # display the tail of the VI screen map define tmap set $h = ((VI_PRIVATE *)$arg0->vi_private)->h_smap set $t = ((VI_PRIVATE *)$arg0->vi_private)->t_smap while ($t >= $h) printf "lno: %2d; soff %d coff %d ", \ (int)$t->lno, (int)$t->soff, (int)$t->coff if ($t->c_ecsize == 0) printf "flushed\n" else printf "\n\tsboff %d; scoff %d\n", \ (int)$t->c_sboff, (int)$t->c_scoff printf "\teboff %d; eclen %d; ecsize %d\n", \ (int)$t->c_eboff, (int)$t->c_eclen, \ (int)$t->c_ecsize end set $t = $t - 1 end end # display the private structures define clp print *((CL_PRIVATE *)sp->gp->cl_private) end define vip print *((VI_PRIVATE *)sp->vi_private) end define exp print *((EX_PRIVATE *)sp->ex_private) end # display the marks define markp set $h = sp->ep->marks.next set $t = &sp->ep->marks while ($h != 0 && $h != $t) printf "key %c lno: %d cno: %d flags: %x\n", \ ((MARK *)$h)->name, ((MARK *)$h)->lno, \ ((MARK *)$h)->cno, ((MARK *)$h)->flags set $h = ((MARK *)$h)->next end end # display the tags define tagp set $h = sp->taghdr.next set $t = &sp->taghdr while ($h != 0 && $h != $t) printf "tag: %s lno %d cno %d\n", ((TAG *)$h)->frp->fname, \ ((TAG *)$h)->lno, ((TAG *)$h)->cno set $h= ((TAG *)$h)->next end end ================================================ FILE: docs/internals/input ================================================ MAPS, EXECUTABLE BUFFERS AND INPUT IN EX/VI: The basic rule is that input in ex/vi is a stack. Every time a key which gets expanded is encountered, it is expanded and the expansion is treated as if it were input from the user. So, maps and executable buffers are simply pushed onto the stack from which keys are returned. The exception is that if the "remap" option is turned off, only a single map expansion is done. I intend to be fully backward compatible with this. Historically, if the mode of the editor changed (ex to vi or vice versa), any queued input was silently discarded. I don't see any reason to either support or not support this semantic. I intend to retain the queued input, mostly because it's simpler than throwing it away. Historically, neither the initial command on the command line (the + flag) or the +cmd associated with the ex and edit commands was subject to mapping. Also, while the +cmd appears to be subject to "@buffer" expansion, once expanded it doesn't appear to work correctly. I don't see any reason to either support or not support these semantics, so, for consistency, I intend to pass both the initial command and the command associated with ex and edit commands through the standard mapping and @ buffer expansion. One other difference between the historic ex/vi and nex/nvi is that nex displays the executed buffers as it executes them. This means that if the file is: set term=xterm set term=yterm set term=yterm the user will see the following during a typical edit session: nex testfile testfile: unmodified: line 3 :1,$yank a :@a :set term=zterm :set term=yterm :set term=xterm :q! This seems like a feature and unlikely to break anything, so I don't intend to match historic practice in this area. The rest of this document is a set of conclusions as to how I believe the historic maps and @ buffers work. The summary is as follows: 1: For buffers that are cut in "line mode", or buffers that are not cut in line mode but which contain portions of more than a single line, a trailing character appears in the input for each line in the buffer when it is executed. For buffers not cut in line mode and which contain portions of only a single line, no additional characters appear in the input. 2: Executable buffers that execute other buffers don't load their contents until they execute them. 3: Maps and executable buffers are copied when they are executed -- they can be modified by the command but that does not change their actions. 4: Historically, executable buffers are discarded if the editor switches between ex and vi modes. 5: Executable buffers inside of map commands are expanded normally. Maps inside of executable buffers are expanded normally. 6: If an error is encountered while executing a mapped command or buffer, the rest of the mapped command/buffer is discarded. No user input characters are discarded. 7: Characters in executable buffers are remapped. 8: Characters in executable buffers are not quoted. Individual test cases follow. Note, in the test cases, control characters are not literal and will have to be replaced to make the test cases work. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 1: For buffers that are cut in "line mode", or buffers that are not cut in line mode but which contain portions of more than a single line, a trailing character appears in the input for each line in the buffer when it is executed. For buffers not cut in line mode and which contain portions of only a single line, no additional characters appear in the input. === test file === 3Gw w line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz === end test file === If the first line is loaded into 'a' and executed: 1G"ayy@a The cursor ends up on the '2', a result of pushing "3Gw^J" onto the stack. If the first two lines are loaded into 'a' and executed: 1G2"ayy@a The cursor ends up on the 'f' in "foo" in the fifth line of the file, a result of pushing "3Gw^Jw^J" onto the stack. If the first line is loaded into 'a', but not using line mode, and executed: 1G"ay$@a The cursor ends up on the '1', a result of pushing "3Gw" onto the stack If the first two lines are loaded into 'a', but not using line mode, and executed: 1G2"ay$@a The cursor ends up on the 'f' in "foo" in the fifth line of the file, a result of pushing "3Gw^Jw^J" onto the stack. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 2: Executable buffers that execute other buffers don't load their contents until they execute them. === test file === cwLOAD B^[ line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz @a@b "byy === end test file === The command is loaded into 'e', and then executed. 'e' executes 'a', which loads 'b', then 'e' executes 'b'. 5G"eyy6G"ayy1G@e The output should be: === output file === cwLOAD B^[ LOAD B 1 foo bar baz line 2 foo bar baz line 3 foo bar baz @a@b "byy === end output file === =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 3: Maps and executable buffers are copied when they are executed -- they can be modified by the command but that does not change their actions. Executable buffers: === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz @a@b "eyy cwEXECUTE B^[ === end test file === 4G"eyy5G"ayy6G"byy1G@eG"ep The command is loaded into 'e', and then executed. 'e' executes 'a', which loads 'e', then 'e' executes 'b' anyway. The output should be: === output file === line 1 foo bar baz EXECUTE B 2 foo bar baz line 3 foo bar baz @a@b "eyy cwEXECUTE B^[ line 1 foo bar baz === end output file === Maps: === test file === Cine 1 foo bar baz line 2 foo bar baz line 3 foo bar baz === end test file === Entering the command ':map = :map = rB^V^MrA^M1G==' shows that the first time the '=' is entered the '=' map is set and the character is changed to 'A', the second time the character is changed to 'B'. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 4: Historically, executable buffers are discarded if the editor switches between ex and vi modes. === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz cwCHANGE^[Q:set set|visual|1Gwww === end test file === vi testfile 4G"ayy@a ex testfile $p yank a @a In vi, the command is loaded into 'a' and then executed. The command subsequent to the 'Q' is (historically, silently) discarded. In ex, the command is loaded into 'a' and then executed. The command subsequent to the 'visual' is (historically, silently) discarded. The first set command is output by ex, although refreshing the screen usually causes it not to be seen. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 5: Executable buffers inside of map commands are expanded normally. Maps inside of executable buffers are expanded normally. Buffers inside of map commands: === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz cwREPLACE BY A^[ === end test file === 4G"ay$:map x @a 1Gx The output should be: === output file === REPLACE BY A 1 foo bar baz line 2 foo bar baz line 3 foo bar baz cwREPLACE BY A^[ === end output file === Maps commands inside of executable buffers: === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz X === end test file === :map X cwREPLACE BY XMAP^[ 4G"ay$1G@a The output should be: === output file === REPLACE BY XMAP 1 foo bar baz line 2 foo bar baz line 3 foo bar baz X === end output file === Here's a test that does both, repeatedly. === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz X Y cwREPLACED BY C^[ blank line === end test file === :map x @a 4G"ay$ :map X @b 5G"by$ :map Y @c 6G"cy$ 1Gx The output should be: === output file === REPLACED BY C 1 foo bar baz line 2 foo bar baz line 3 foo bar baz X Y cwREPLACED BY C^[ blank line === end output file === =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 6: If an error is encountered while executing a mapped command or a buffer, the rest of the mapped command/buffer is discarded. No user input characters are discarded. === test file === line 1 foo bar baz line 2 foo bar baz line 3 foo bar baz :map = 10GcwREPLACMENT^V^[^[ === end test file === The above mapping fails, however, if the 10G is changed to 1, 2, or 3G, it will succeed. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 7: Characters in executable buffers are remapped. === test file === abcdefghijklmnnop ggg === end test file === :map g x 2G"ay$1G@a The output should be: === output file === defghijklmnnop ggg === end output file === =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 8: Characters in executable buffers are not quoted. === test file === iFOO^[ === end test file === 1G"ay$2G@a The output should be: === output file === iFOO^[ FOO === end output file === =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ================================================ FILE: docs/internals/input.license ================================================ # $OpenBSD: input,v 1.2 2001/01/29 01:58:38 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # @(#)input 5.5 (Berkeley) 7/2/94 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/openmode ================================================ # SPDX-License-Identifier: BSD-3-Clause # @(#)openmode 8.1 (Berkeley) 10/29/94 Open mode has the following special behaviors: z, ^F, ^B: If count is not specified, it shall default to the window edit option - 2. Write lines from the edit buffer starting at: (the current line) - ((count - 2) / 2) until: (((count + 1) / 2) * 2) - 1 lines, or the last line in the edit buffer has been written. A line consisting of the smaller of the number of columns in the display divided by two or 40 ``-'' characters shall be written immediately before and after the specified is written. These two lines shall count against the total number of lines to be written. A blank line shall be written after the last line is written. z, ^F and ^B all behave identically. ^D: Display the next scroll value lines, change the current line. ^U: Change the current line, do nothing else. ^E, ^Y: Do nothing. ^L: Clear the screen and re-display the current line. H, L, M: Move to the first nonblank of the current line and do nothing else. ================================================ FILE: docs/internals/openmode.license ================================================ # SPDX-License-Identifier: BSD-3-Clause # @(#)openmode 8.1 (Berkeley) 10/29/94 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/quoting ================================================ QUOTING IN EX/VI: There are four escape characters in historic ex/vi: \ (backslashes) ^V ^Q (assuming it wasn't used for IXON/IXOFF) The terminal literal next character. Vi did not use the lnext character, it always used ^V (or ^Q). ^V and ^Q were equivalent in all cases for vi. There are four different areas in ex/vi where escaping characters is interesting: 1: In vi text input mode. 2: In vi command mode. 3: In ex command and text input modes. 4: In the ex commands themselves. 1: Vi text input mode (a, i, o, :colon commands, etc.): The set of characters that users might want to escape are as follows: As ^L and ^Z were not special in input mode, they are not listed. carriage return (^M) escape (^[) autoindents (^D, 0, ^, ^T) erase (^H) word erase (^W) line erase (^U) newline (^J) (not historic practice) Historic practice was that ^V was the only way to escape any of these characters, and that whatever character followed the ^V was taken literally, e.g. ^V^V is a single ^V. I don't see any strong reason to make it possible to escape ^J, so I'm going to leave that alone. One comment regarding the autoindent characters. In historic vi, if you entered "^V0^D" autoindent erasure was still triggered, although it wasn't if you entered "0^V^D". In nvi, if you escape either character, autoindent erasure is not triggered. Abbreviations were not performed if the non-word character that triggered the abbreviation was escaped by a ^V. Input maps were not triggered if any part of the map was escaped by a ^V. The historic vi implementation for the 'r' command requires two leading ^V's to replace a character with a literal character. This is obviously a bug, and should be fixed. 2: Vi command mode Command maps were not triggered if the second or later character of a map was escaped by a ^V. The obvious extension is that ^V should keep the next command character from being mapped, so you can do ":map x xxx" and then enter ^Vx to delete a single character. 3: Ex command and text input modes. As ex ran in canonical mode, there was little work that it needed to do for quoting. The notable differences between ex and vi are that it was possible to escape a in the ex command and text input modes, and ex used the "literal next" character, not control-V/control-Q. 4: The ex commands: Ex commands are delimited by '|' or newline characters. Within the commands, whitespace characters delimit the arguments. Backslash will generally escape any following character. In the abbreviate, unabbreviate, map and unmap commands, control-V escapes the next character, instead. This is historic behavior in vi, although there are special cases where it's impossible to escape a character, generally a whitespace character. Escaping characters in file names in ex commands: :cd [directory] (directory) :chdir [directory] (directory) :edit [+cmd] [file] (file) :ex [+cmd] [file] (file) :file [file] (file) :next [file ...] (file ...) :read [!cmd | file] (file) :source [file] (file) :write [!cmd | file] (file) :wq [file] (file) :xit [file] (file) Since file names are also subject to word expansion, the underlying shell had better be doing the correct backslash escaping. This is NOT historic behavior in vi, making it impossible to insert a whitespace, newline or carriage return character into a file name. 4: Escaping characters in non-file arguments in ex commands: :abbreviate word string (word, string) * :edit [+cmd] [file] (+cmd) * :ex [+cmd] [file] (+cmd) :map word string (word, string) * :set [option ...] (option) * :tag string (string) :unabbreviate word (word) :unmap word (word) These commands use whitespace to delimit their arguments, and use ^V to escape those characters. The exceptions are starred in the above list, and are discussed below. In general, I intend to treat a ^V in any argument, followed by any character, as that literal character. This will permit editing of files name "foo|", for example, by using the string "foo\^V|", where the literal next character protects the pipe from the ex command parser and the backslash protects it from the shell expansion. This is backward compatible with historical vi, although there were a number of special cases where vi wasn't consistent. 4.1: The edit/ex commands: The edit/ex commands are a special case because | symbols may occur in the "+cmd" field, for example: :edit +10|s/abc/ABC/ file.c In addition, the edit and ex commands have historically ignored literal next characters in the +cmd string, so that the following command won't work. :edit +10|s/X/^V / file.c I intend to handle the literal next character in edit/ex consistently with how it is handled in other commands. More fun facts to know and tell: The acid test for the ex/edit commands: date > file1; date > file2 vi :edit +1|s/./XXX/|w file1| e file2|1 | s/./XXX/|wq No version of vi, of which I'm aware, handles it. 4.2: The set command: The set command treats ^V's as literal characters, so the following command won't work. Backslashes do work in this case, though, so the second version of the command does work. set tags=tags_file1^V tags_file2 set tags=tags_file1\ tags_file2 I intend to continue permitting backslashes in set commands, but to also permit literal next characters to work as well. This is backward compatible, but will also make set consistent with the other commands. I think it's unlikely to break any historic .exrc's, given that there are probably very few files with ^V's in their name. 4.3: The tag command: The tag command ignores ^V's and backslashes; there's no way to get a space into a tag name. I think this is a don't care, and I don't intend to fix it. 5: Regular expressions: :global /pattern/ command :substitute /pattern/replace/ :vglobal /pattern/ command I intend to treat a backslash in the pattern, followed by the delimiter character or a backslash, as that literal character. This is historic behavior in vi. It would get rid of a fairly hard-to-explain special case if we could just use the character immediately following the backslash in all cases, or, if we changed nvi to permit using the literal next character as a pattern escape character, but that would probably break historic scripts. There is an additional escaping issue for regular expressions. Within the pattern and replacement, the '|' character did not delimit ex commands. For example, the following is legal. :substitute /|/PIPE/|s/P/XXX/ This is a special case that I will support. 6: Ending anything with an escape character: In all of the above rules, an escape character (either ^V or a backslash) at the end of an argument or file name is not handled specially, but used as a literal character. ================================================ FILE: docs/internals/quoting.license ================================================ # $OpenBSD: quoting,v 1.3 2001/01/29 01:58:39 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # @(#)quoting 5.5 (Berkeley) 11/12/94 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/internals/structures ================================================ There are three major data structures in this package, plus a single data structure per screen type. The first is a single global structure (GS) which contains information common to all files and screens. It hold global things like the input key queues, and functions as a single place to hang things. For example, interrupt routines have to be able to find screen structures, and they can only do this if they have a starting point. The number of globals in nvi is dependent on the screen type, but every screen type will have at least one global, __global_list, which references the GS structure. The GS structure contains linked lists of screen (SCR) structures. Each SCR structure normally references a file (EXF) structure. The GS structure has a set of functions which update the screen and/or return information about the screen from the underlying screen package. The GS structure never goes away. The SCR structure persists over instances of screens, and the EXF structure persists over references to files. File names have different properties than files themselves, so the name information for a file is held in an FREF structure which is chained from the SCR structure. In general, functions are always passed an SCR structure, which usually references an underlying EXF structure. The SCR structure is necessary for any routine that wishes to talk to the screen, the EXF structure is necessary for any routine that wants to modify the file. The relationship between an SCR structure and its underlying EXF structure is not fixed, and various ex commands will substitute a new EXF in place of the current one, and there's no way to detect this. The naming of the structures is consistent across the program. (Macros even depend on it, so don't try and change it!) The global structure is "gp", the screen structure is "sp", and the file structure is "ep". A few other data structures: TEXT In nvi/cut.h. This structure describes a portion of a line, and is used by the input routines and as the "line" part of a cut buffer. CB In nvi/cut.h. A cut buffer. A cut buffer is a place to hang a list of TEXT structures. CL The curses screen private data structure. Everything to do standalone curses screens. MARK In nvi/mark.h. A cursor position, consisting of a line number and a column number. MSG In nvi/msg.h. A chain of messages for the user. SEQ In nvi/seq.h. An abbreviation or a map entry. EXCMD In nvi/ex/ex.h. The structure that gets passed around to the functions that implement the ex commands. (The main ex command loop (see nvi/ex/ex.c) builds this up and then passes it to the ex functions.) VICMD In nvi/vi/vi.h. The structure that gets passed around to the functions that implement the vi commands. (The main vi command loop (see nvi/vi/vi.c) builds this up and then passes it to the vi functions.) ================================================ FILE: docs/internals/structures.license ================================================ # $OpenBSD: structures,v 1.3 2001/01/29 01:58:39 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # @(#)structures 5.4 (Berkeley) 10/4/95 Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/tutorial/vi.advanced ================================================ Section 26: Index to the rest of the tutorial The remainder of the tutorial can be perused at your leisure. Simply find the topic of interest in the following list, and {/Section xx:/^M} to get to the appropriate section. (Remember that ^M means the return key) The material in the following sections is not necessarily in a bottom up order. It should be fairly obvious that if a section mentions something with which you are not familiar, say, buffers, you might {/buffer/^M} followed by several {n} to do a keyword search of the file for more details on that item. Another point to remember is that commands are surrounded by curly-braces and can therefore be found rather easily. To see where, say, the X command is used try {/{X}/^M}. Subsequent {n} will show you other places the command was used. We have tried to maintain the convention of placing the command letter surrounded by curly-braces on the section line where that command is mentioned. Finally, you should have enough 'savvy' at this point to be able to do your own experimentation with commands without too much hand-holding on the part of the tutorial. Experimentation is the best way to learn the effects of the commands. Section Topic - description ------- ------------------- (Sections 1 through 25 are located in the file vi.beginner.) 1 introduction: {^F} {ZZ} 2 introduction (cont'd) and positioning: {^F} {^B} 3 introduction (cont'd) and positioning: {^F} {^B} 4 positioning: {^F} {^B} {^M} (return key) 5 quitting: {:q!} {^M} (return key) 6 marking, cursor and screen positioning: {m} {G} {'} {z} 7 marking, cursor and screen positioning: {m} {G} {'} {z} 8 marking, cursor and screen positioning: {z} {m} {'} 9 marking and positioning: {m} {''} 10 line positioning: {^M} {-} 11 scrolling with {^M} 12 scrolling with {-} and screen adjustment {z} 13 notes on use of tutorial 14 other scrolling and positioning commands: {^E} {^Y} {^D} {^U} 15 searching: {/ .. /^M} 16 searching: {? .. ?^M} {n} (in search strings ^ $) 17 searching: \ and magic-characters in search strings 18 colon commands, exiting: {:} {ZZ} 19 screen positioning: {H} {M} {L} 20 character positioning: {w} {b} {0} {W} {B} {e} {E} {'} {`} 21 cursor positioning: {l} {k} {j} {h} 22 adding text: {i} {a} {I} {A} {o} {O} {^[} (escape key) 23 character manipulation: {f} {x} {X} {w} {l} {r} {R} {s} {S} {J} 24 undo: {u} {U} 25 review (The following sections are in this file.) 26 Index to the rest of the tutorial ******** YOU ARE HERE ******* 27 discussion of repeat counts and the repeat command: {.} 28 more on low-level character motions: {t} {T} {|} 29 advanced correction operators: {d} {c} 30 updating the screen: {^R} 31 text buffers: {"} 32 rearranging and duplicating text: {p} {P} {y} {Y} 33 recovering lost lines 34 advanced file manipulation with vi 34.1 more than one file at a time: {:n} 34.2 reading files and command output: {:r} 34.3 invoking vi from within vi: {:e} {:vi} 34.4 escaping to a shell: {:sh} {:!} 34.5 writing parts of a file: {:w} 34.6 filtering portions of text: {!} 35 advanced searching: magic patterns 36 advanced substitution: {:s} 37 advanced line addressing: {:p} {:g} {:v} 38 higher level text objects and nroff: ( ) { } [[ ]] 39 more about inserting text 40 more on operators: {d} {c} {<} {>} {!} {=} {y} 41 abbreviations: {:ab} 42 vi's relationship with the ex editor: {:} 43 vi on hard-copy terminals and dumb terminals: open mode 44 options: {:set} {setenv EXINIT} 44.1 autoindent 44.2 autoprint 44.3 autowrite 44.4 beautify 44.5 directory 44.6 edcompatible 44.7 errorbells 44.8 hardtabs 44.9 ignorecase 44.10 lisp 44.11 list 44.12 magic 44.13 mesg 44.14 number 44.15 open 44.16 optimize 44.17 paragraphs 44.18 prompt 44.19 readonly 44.20 redraw 44.21 remap 44.22 report 44.23 scroll 44.24 sections 44.25 shell 44.26 shiftwidth 44.27 showmatch 44.28 slowopen 44.29 tabstop 44.30 tags 44.31 taglength 44.32 term 44.33 terse 44.34 timeout 44.35 ttytype 44.36 warn 44.37 window 44.38 wrapscan 44.39 wrapmargin 44.40 writeany 44.41 w300, w1200, w9600 Section 27: repetition counts and the repeat command {.} Most vi commands will use a preceding count to affect their behavior in some way. We have already seen how {3x} deletes three characters, and {22G} moves us to line 22 of the file. For almost all of the commands, one can survive by thinking of these leading numbers as a 'repeat count' specifying that the command is to be repeated so many number of times. Other commands use the repeat count slightly differently, like the {G} command which use it as a line number. For example: {3^D} means scroll down in the file three lines. Subsequent {^D} OR {^U} will scroll only three lines in their respective directions! {3z^M} says put line three of the file at the top of the screen, while {3z.} says put line three as close to the middle of the screen as possible. {50|} moves the cursor to column fifty in the current line. {3^F} says move forward 3 screen-fulls. This is a repetition count. The documents advertise that {3^B} should move BACK three screen-fulls, but I can't get it to work. Position the cursor on some text and try {3r.}. This replaces three characters with '...'. However, {3s.....^[} is the same as {3xi.....^[}. Try {10a+----^[}. A very useful instance of a repetition count is one given to the '.' command, which repeats the last 'change' command. If you {dw} and then {3.}, you will delete first one and then three words. You can then delete two more words with {2.}. If you {3dw}, you will delete three words. A subsequent {.} will delete three more words. But a subsequent {2.} will delete only two words, not three times two words. Caveat: The author has noticed that any repetition count with {^B} will NOT work: indeed, if you are at the end of your file and try {3^B} sufficiently often, the editor will hang you in an infinite loop. Please don't try it: take my word for it. Section 28: {t} {T} {|} Position the cursor on line 13 below: Line 13: Four score and seven years ago, our forefathers brought ... Note that {fv} moves the cursor on/over the 'v' in 'seven'. Do a {0} to return to the beginning of the line and try a {tv}. The cursor is now on/over the first 'e' in 'seven'. The {f} command finds the next occurrence of the specified letter and moves the cursor to it. The {t} command finds the specified letter and moves the cursor to the character immediately preceding it. {T} searches backwards, as does {F}. Now try {60|}: the cursor is now on the 'o' in 'brought', which is the sixtieth character on the line. Section 29: {d} {c} Due to their complexity we have delayed discussion of two of the most powerful operators in vi until now. Effective use of these operators requires more explanation than was deemed appropriate for the first half of the tutorial. {d} and {c} are called operators instead of commands because they consist of three parts: a count specification or a buffer specification (see section #BUFFERS), the {d} or {c}, and the object or range description. We will not discuss buffers at this stage, but will limit ourselves to count specifications. Examples speak louder than words: position the cursor at the beginning of line 14: Line 14: Euclid alone has looked on beauty bear. Obviously, there is something wrong with this quotation. Type {2fb} to position the cursor on the 'b' of 'bear'. Now, type {cwbare^[} and observe the results. The {cw} specifies that the change command {c} is to operate on a word object. More accurately, it specifies that the range of the change command includes the next word. Position the cursor on the period in Line 14. (one way is to use {f.}) Now, type {cbbeast^[}. This specifies the range of the change command to be the previous word (the 'b' reminiscent of the {b} command). If we had wished to delete the word rather than change it, we would have used the {d} operator, rather than the {c} operator. Position the cursor at the beginning of the line with {0}. Type {d/look/^M}. The search string specified the range of the delete. Everything UP TO the word 'looking' was deleted from the line. In general, almost any command that would move the cursor will specify a range for these commands. The most confusing exception to this rule is when {dd} or {cc} is entered: they refer to the whole line. Following is a summary of the suffixes (suffices? suffici?) and the ranges they specify: suffix will delete{d}/change{c} ------ ------------------------ ^[ cancels the command w the word to the right of the cursor W ditto, but ignoring punctuation b the word to the left of the cursor B ditto, but ignoring punctuation e see below. E ditto (space) a character $ to the end of the line ^ to the beginning of the line / .. / up to, but not including, the string ? .. ? back to and including the string fc up to and including the occurrence of c Fc back to and including the occurrence of c tc up to but not including the occurrence of c Tc back to but not including the occurrence of c ^M TWO lines (that's right: two) (number)^M that many lines plus one (number)G up to and including line (number) ( the previous sentence if you are at the beginning of the current sentence, or the current sentence up to where you are if you are not at the beginning of the current sentence. Here, 'sentence' refers to the intuitive notion of an English sentence, ending with '!', '?', or '.' and followed by an end of line or two spaces. ) the rest of the current sentence { analogous to '(', but in reference to paragraphs: sections of text surrounded by blank lines } analogous to ')', but in reference to paragraphs [[ analogous to '(', but in reference to sections ]] analogous to ')', but in reference to sections H the first line on the screen M the middle line on the screen L the last line on the screen 3L through the third line from the bottom of the screen ^F forward a screenful ^B backward a screenful : : etc. etc. etc. This list is not exhaustive, but it should be sufficient to get the idea across: after the {c} or {d} operator, you can specify a range with another move-the-cursor command, and that is the region of text over which the command will be effective. Section 30: updating the screen {^R} Vi tries to be very intelligent about the type of terminal you are working on and tries to use the in-terminal computing power (if any) of your terminal. Also if the terminal is running at a low baud rate (say 1200 or below), vi sets various parameters to make things easier for you. For example, if you were running on a 300 baud terminal (that's 30 characters per second transmission rate) not all 24 lines of the screen would be used by vi. In addition, there is a large portion of the editor keeping track of what your screen currently looks like, and what it would look like after a command has been executed. Vi then compares the two, and updates only those portions of the screen that have changed. Furthermore, some of you may have noticed (it depends on your terminal) that deleting lines or changing large portions of text may leave some lines on the screen looking like: @ meaning that this line of the screen does not correspond to any line in your file. It would cost more to update the line than to leave it blank for the moment. If you would like to see your screen fully up-to-date with the contents of your file, type {^R}. To see it in action, delete several lines with {5dd}, type {^R}, and then type {u} to get the lines back. Here is as good a place as any to mention that if the editor is displaying the end of your file, there may be lines on the screen that look like: ~ indicating that that screen line would not be affected by {^R}. These lines simply indicate the end of the file. Section 31: text buffers {"} Vi gives you the ability to store text away in "buffers". This feature is very convenient for moving text around in your file. There are a total of thirty- five buffers available in vi. There is the "unnamed" buffer that is used by all commands that delete text, including the change operator {c}, the substitute and replace commands {s} and {r}, as well as the delete operator {d} and delete commands {x} and {X}. This buffer is filled each time any of these commands are used. However, the undo command {u} has no effect on the unnamed buffer. There are twenty-six buffers named 'a' through 'z' which are available for the user. If the name of the buffer is capitalized, then the buffer is not overwritten but appended to. For example, the command {"qdd} will delete one line and store that line in the 'q' buffer, destroying the previous contents of the buffer. However, {"Qdd} will delete one line of text and append that line to the current contents of the 'q' buffer. Finally, there are nine buffers named '1' through '9' in which the last nine deletes are stored. Buffer 1 is the default buffer for the modify commands and is sometimes called the unnamed buffer. To reference a specific buffer, use the double-quote command {"} followed by the name of the buffer. The next two sections show how buffers can be used to advantage. Section 32: rearranging and duplicating text: {y} {Y} {p} {P} Position yourself on line 15 below and {z^M}: Line 15: A tree as lovely as a poem ... Line 16: I think that I shall never see Type {dd}. Line 15 has disappeared and been replaced with the empty line (one with the single character @ on it) or (again depending on your terminal) Line 16 has moved up and taken its place. We could recover Line 15 with an undo {u} but that would simply return it to its original location. Obviously, the two lines are reversed, so we want to put line 15 AFTER line 16. This is simply done with the put command {p}, which you should type now. What has happened is that {dd} put Line 15 into the unnamed buffer, and the {p} command retrieved the line from the unnamed buffer. Now type {u} and observe that Line 15 disappears again (the put was undone without affecting the unnamed buffer). Type {P} and see that the capital {P} puts the line BEFORE the cursor. To get Line 15 where it belongs again type {dd}{p}. Also in Line 15 note that the words 'tree' and 'poem' are reversed. Using the unnamed buffer again: {ft}{dw}{ma}{fp}{P}{w}{dw}{`aP} will set things aright (note the use of the reverse quote). The put commands {p} and {P} do not affect the contents of the buffer. Therefore, multiple {p} or {P} will put multiple copies of the unnamed buffer into your file. Experiment with {d} and {p} on words, paragraphs, etc. Whatever {d} deletes, {p} can put. Position the cursor on Line 17 and {z^M}: Line 17: interest apple cat elephant boy dog girl hay farmer Our task is to alphabetize the words on line 17. With the named buffers (and a contrived example) it is quite easy: {"idw}{"adw}{"cdw}{"edw}{"bdw}{"ddw}{"gdw}{"hdw}{"fdw} stores each of the words in the named buffer corresponding to the first letter of each of the words ('interest' goes in buffer "i, 'apple' goes in buffer "a, etc.). Now to put the words in order type: {"ap$}{"bp$}{"cp$}{"dp$}{"ep$}{"fp$}{"gp$}{"hp$}{"ip$} Notice that, because 'farmer' was at the end of the line, {dw} did not include a space after it, and that, therefore, there is no space between 'farmer' and 'girl'. This is corrected with {Fg}{i ^[}. This example could have been done just as easily with lines as with words. You do not have to delete the text in order to put it into a buffer. If all you wish to do is to copy the text somewhere else, don't use {d}, rather use the yank commands {y} or {Y}. {y} is like {d} and {c} - an operator rather than a command. It, too, takes a buffer specification and a range specification. Therefore, instead of {dw}{P} to load the unnamed buffer with a word without deleting the word, use {yw} (yank a word). {Y} is designed yank lines, and not arbitrary ranges. That is, {Y} is equivalent to {yy} (remember that operators doubled means the current line), and {3Y} is equivalent to {3yy}. If the text you yank or modify forms a part of a line, or is an object such as a sentence which partially spans more than one line, then when you put the text back, it will be placed after the cursor (or before if you use {P}). If the yanked text forms whole lines, they will be put back as whole lines, without changing the current line. In this case, the put acts much like the {o} or {O} command. The named buffers "a through "z are not affected by changing edit files. However, the unnamed buffer is lost when you change files, so to move text from one file to another you should use a named buffer. Section 33: recovering lost lines Vi also keeps track of the last nine deletes, whether you ask for it or not. This is very convenient if you would like to recover some text that was accidentally deleted or modified. Position the cursor on line 18 following, and {z^M}. Line 18: line 1 Line 19: line 2 Line 20: line 3 Line 21: line 4 Line 22: line 5 Line 23: line 6 Line 24: line 7 Line 25: line 8 Line 26: line 9 Type {dd} nine times: now don't cheat with {9dd}! That is totally different. The command {"1p} will retrieve the last delete. Furthermore, when the numbered buffers are used, the repeat-command command {.} will increment the buffer numbers before executing, so that subsequent {.} will recover all nine of the deleted lines, albeit in reverse order. If you would like to review the last nine deletes without affecting the buffers or your file, do an undo {u} after each put {p} and {.}: {"1p}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.}{u}{.} will show you all the buffers and leave them and your file intact. If you had cheated above and deleted the nine lines with {9dd}, all nine lines would have been stored in both the unnamed buffer and in buffer number 1. (Obviously, buffer number 1 IS the unnamed buffer and is just the default buffer for the modify commands.) Section 34: advanced file manipulation: {:r} {:e} {:n} {:w} {!} {:!} We've already looked at writing out the file you are editing with the {:w} command. Now let's look at some other vi commands to make editing more efficient. Section 34.1: more than one file at a time {:n} {:args} Many times you will want to edit more than one file in an editing session. Instead of entering vi and editing the first file, exiting, entering vi and editing the second, etc., vi will allow you to specify ALL files that you wish to edit on the invocation line. Therefore, if you wanted to edit file1 and file2: % vi file1 file2 will set up file1 for editing. When you are done editing file one, write it out {:w^M} and then type {:n^M} to get the next file on the list. On large programming projects with many source files, it is often convenient just to specify all source files with, say: % vi *.c If {:n^M} brings in a file that does not need any editing, another {:n^M} will bring in the next file. If you have made changes to the first file, but decide to discard these changes and proceed to the next file, {:n!^M} forces the editor to discard the current contents of the editor. You can specify a new list of files after {:n}; e.g., {:n f1 f2 f3^M}. This will replace the current list of files (if any). You can see the current list of files being edited with {:args^M}. Section 34.2: reading files and command output: {:r} Typing {:r fname^M} will read the contents of file fname into the editor and put the contents AFTER the cursor line. Typing {:r !cmd^M} will read the output of the command cmd and place that output after the cursor line. Section 34.3: invoking vi from within vi: {:e} {:vi} To edit another file not mentioned on the invocation line, type {:e filename^M} or {:vi filename^M}. If you wish to discard the changes to the current file, use the exclamation point after the command, e.g. {:e! filename^M}. Section 34.4: escaping to a shell: {:sh} {:!} {^Z} Occasionally, it is useful to interrupt the current editing session to perform a UNIX task. However, there is no need to write the current file out, exit the editor, perform the task, and then re-invoke the editor on the same file. One thing to do is to spin off another process. If there are several UNIX commands you will need to execute, simply create another shell with {:sh^M}. At this point, the editor is put to sleep and will be reawakened when you log out of the shell. If it is a single command that you want to execute, type {:!cmd^M}, where cmd is the command that you wish to run. The output of the command will come to the terminal as normal, and will not be made part of your file. The message "[Hit return to continue]" will be displayed by vi after the command is finished. Hitting return will then repaint the screen. Typing another {:!cmd^M} at this point is also acceptable. However, there is a quicker, easier way: type {^Z}. Now this is a little tricky, but hang in there. When you logged into UNIX, the first program you began communicating with was a program that is called a "shell" (i.e. it 'lays over' the operating system protecting you from it, sort of like a considerate porcupine). When you got your first prompt on the terminal (probably a '%' character) this was the shell telling you to type your first command. When you typed {vi filename} for some file, the shell did not go away, it just went to sleep. The shell is now the parent of vi. When you type {^Z} the editor goes to sleep, the shell wakes up and says "you rang?" in the form of another prompt (probably '%'). At this point you are talking to the shell again and you can do anything that you could before including edit another file! (The only thing you can't do is log out: you will get the message "There are stopped jobs.") When your business with the shell is done, type {fg} for 'foreground' and the last process which you ^Z'd out of will be reawakened and the shell will go back to sleep. I will refer you to the documentation for the Berkeley shell 'csh' for more information on this useful capability. Section 34.5: writing parts of a file: {:w} The {:w} command will accept a range specifier that will then write only a selected range of lines to a file. To write this section to a file, position the cursor on the section line (e.g. {/^Section 34.5:/^M}) and {z^M}. Now type {^G} to find out the line number (it will be something like "line 513"). Now {/^Section 34.6:/-1^M} to find the last line of this section, and {^G} to find its line number (it will be something like 542). To write out this section of text by itself to a separate file which we will call "sepfile", type {:510,542w sepfile^M}. If sepfile already exists, you will have to use the exclamation point: {:1147,1168w! sepfile^M} or write to a different, non- existent file. {:!cat sepfile^M} will display the file just written, and it should be the contents of this section. There is an alternate method of determining the line numbers for the write. {:set number^M} will repaint the screen with each line numbered. When the file is written and the numbers no longer needed, {:set nonumber^M} will remove the numbers, and {^R} will adjust the screen. Or, if you remember your earlier lessons about marking lines of text, mark the beginning and ending lines. Suppose we had used {ma} to mark the first line of the section and {mb} to mark the last. Then the command {:'a,'bw sepfile^M} will write the section into "sepfile". In general, you can replace a line number with the 'name' of a marked line (a single-quote followed by the letter used to mark the line) Section 34.6: filtering portions of text: {!} {!} is an operator like {c} and {d}. That is, it consists of a repetition count, {!}, and a range specifier. Once the {!} operator is entered in its entirety, a prompt will be given at the bottom of the screen for a UNIX command. The text specified by the {!} operator is then deleted and passed/filtered/piped to the UNIX command you type. The output of the UNIX command is then placed in your file. For example, place the cursor at the beginning of the following line and {z^M}: ls -l vi.tutorial ********* marks the bottom of the output from the ls command ********** Now type {!!csh^M}. The line will be replaced with the output from the ls command. The {u} command works on {!}, also. Here is an extended exercise to display some of these capabilities. When this tutorial was prepared, certain auxiliary programs were created to aid in its development. Of major concern was the formatting of sections of the tutorial to fit on a single screen, particularly the first few sections. What was needed was a vi command that would 'format' a paragraph; that is, fill out lines with as many words as would fit in eighty columns. There is no such vi command. Therefore, another method had to be found. Of course, nroff was designed to do text formatting. However, it produces a 'page'; meaning that there may be many blank lines at the end of a formatted paragraph from nroff. The awk program was used to strip these blank lines from the output from nroff. Below are the two files used for this purpose: I refer you to documentation on nroff and awk for a full explanation of their function. Position the cursor on the next line and {z^M}. ******** contents of file f ********** # nroff -i form.mac | awk "length != 0 { print }" ***** contents of file form.mac ****** .na .nh .ll 79 .ec  .c2  .cc  ************************************** Determine the line numbers of the two lines of file f. They should be something like 574 and 575, although you better double check: this file is under constant revision and the line numbers may change inadvertently. Then {:574,575w f^M}. Do the same for the lines of file form.mac. They will be approximately 577 and 582. Then {:577,582w form.mac^M}. File f must have execute privileges as a shell file: {:!chmod 744 f^M}. Observe that this paragraph is rather ratty in appearance. With our newly created files we can clean it up dramatically. Position the cursor at the beginning of this paragraph and type the following sequence of characters (note that we must abandon temporarily our convention of curly braces since the command itself contains a curly brace - we will use square brackets for the nonce): [!}f^M]. Here is a brief explanation of what has happened. By typing [!}f^M] we specified that the paragraph (all text between the cursor and the first blank line) will be removed from the edit file and piped to a UNIX program called "f". This is a shell command file that we have created. This shell file runs nroff, pipes its output to awk to remove blank lines, and the output from awk is then read back into our file in the place of the old, ratty paragraph. The file form.mac is a list of commands to nroff to get it to produce paragraphs to our taste (the right margin is not justified, the line is 79 characters long, words are not hyphenated, and three nroff characters are renamed to avoid conflict: note that in this file, the {^G} you see there is vi's display of the control-G character, and not the two separate characters ^ up-arrow and G upper-case g). This example was created before the existence of the fmt program. I now type [!}fmt^M] to get the same effect much faster. Actually, I don't type those six keys each time: I have an abbreviation (which see). Section 35: searching with magic patterns The documentation available for "magic patterns" (i.e. regular expressions) is very scanty. The following should explain this possibly very confusing feature of the editor. This section assumes that the magic option is on. To make sure, you might want to type {:set magic^M}. By "magic pattern" we mean a general description of a piece of text that the editor attempts to find during a search. Most search patterns consist of strings of characters that must be matched exactly, e.g. {/card/^M} searches for a specific string of four characters. Let us suppose that you have discovered that you consistently have mistyped this simple word as either ccrd or czrd (this is not so far-fetched for touch typists). You could {/ccrd/^M} and {n} until there are no more of this spelling, followed by {/czrd/^M} and {n} until there are no more of these. Or you could {/c.rd/^M} and catch all of them on the first pass. Try typing {/c.rd/^M} followed by several {n} and observe the effect. Line 27: card cord curd ceard When '.' is used in a search string, it has the effect of matching any single character. The character '^' (up-arrow) used at the beginning of a search string means the beginning of the line. {/^Line 27/^M} will find the example line above, while {/Line 27/^M} will find an occurrence of this string anywhere in the line. Similarly, {/ the$/^M} will find all occurrences of the word 'the' occurring at the end of a line. There are several of them in this file. Note that {:set nomagic^M} will turn off the special meaning of these magic characters EXCEPT for '^' and '$' which retain their special meanings at the beginning and end of a search string. Within the search string they hold no special meaning. Try {/\/ the$\//^M} and note that the dollar-sign is not the last character in the search string. Let the dollar-sign be the last character in the search string, as in {/\/ the$/^M} and observe the result. Observe the result of {/back.*file/^M}. This command, followed by sufficient {n}, will show you all lines in the file that contain both the words 'back' and 'file' on the same line. The '*' magic character specifies that the previous regular expression (the '.' in our example) is to be repeatedly matched zero or more times. In our example we specified that the words 'back' and 'file' must appear on the same line (they may be parts of words such as 'backwards' or 'workfile') separated by any number (including zero) of characters. We could have specified that 'back' and 'file' are to be words by themselves by using the magic sequences '\<' or '\>'. E.g. {/\.*\/^M}. The sequence '\<' specifies that this point of the search string must match the beginning of a word, while '\>' specifies a match at the end of a word. By surrounding a string with these characters we have specified that they must be words. To find all words that begin with an 'l' or a 'w', followed by an 'a' or an 'e', and ending in 'ing', try {/\<[lw][ea][a-z]*ing\>/^M}. This will match words like 'learning', 'warning', and 'leading'. The '[..]' notation matches exactly ONE character. The character matched will be one of the characters enclosed in the square brackets. The characters may be specified individually as in [abcd] or a '-' may be used to specify a range of characters as in [a-d]. That is, [az] will match the letter 'a' OR the letter 'z', while [a-z] will match any of the lower case letters from 'a' through 'z'. If you would like to match either an 'a', a '-', or a 'z', then the '-' must be escaped: [a\-z] will match ONE of the three characters 'a', '-', or 'z'. If you wish to find all Capitalized words, try {/\<[A-Z][a-z]*\>/^M}. The following will find all character sequences that do NOT begin with an un-capitalized letter by applying a special meaning to the '^' character in square brackets: {/\<[^a-z][a-z]*\>/^M}. When '^' is the first character of a square-bracket expression, it specifies "all but these characters". (No one claimed vi was consistent.) To find all variable names (the first character is alphabetic, the remaining characters are alphanumeric): try {/\<[A-Za-z][A-Za-z0-9]*\>/^M}. In summary, here are the primitives for building regular expressions: ^ at beginning of pattern, matches beginning of line $ at end of pattern, matches end of line . matches any single character \< matches the beginning of a word \> matches the end of a word [str] matches any single character in str [^str] matches any single character NOT in str [x-y] matches any character in the ASCII range between x and y * matches any number (including zero) of the preceding pattern Section 36: advanced substitution: {:s} The straightforward colon-substitute command looks like the substitute command of most line-oriented editors. Indeed, vi is nothing more than a superstructure on the line-oriented editor ex and the colon commands are simply a way of accessing commands within ex (see section #EX). This gives us a lot of global file processing not usually found in visual oriented editors. The colon-substitute command looks like: {:s/ .. / .. /^M} and will find the pattern specified after the first slash (this is called the search pattern), and replace it with the pattern specified after the second slash (called, obviously enough, the replacement pattern). E.g. position the cursor on line 28 below and {:s/esample/example/^M}: Line 28: This is an esample. The {u} and {U} commands work for {:s}. The first pattern (the search pattern) may be a regular expression just as for the search command (after all, it IS a search, albeit limited to the current line). Do an {u} on the above line, and try the following substitute, which will do almost the same thing: {:s/s[^ ]/x/^M}. Better undo it with {u}. The first pattern {s[^ ]} matches an 's' NOT followed by a blank: the search therefore ignores the 's'es in 'This' and 'is'. However, the character matched by {[^ ]} must appear in the replacement pattern. But, in general, we do not know what that character is! (In this particular example we obviously do, but more complicated examples will follow.) Therefore, vi (really ex) has a duplication mechanism to copy patterns matched in the search string into the replacement string. Line 29 below is a copy of line 28 above so you can adjust your screen. Line 29: This is an esample. In general, you can nest parts of the search pattern in \( .. \) and refer to it in the replacement pattern as \n, where n is a digit. The problem outlined in the previous paragraph is solved with {:s/s\([^ ]\)/x\1/^M}: try it. Here \1 refers to the first pattern grouping \( .. \) in the search string. Obviously, for a single line, this is rather tedious. Where it becomes powerful, if not necessary, is in colon-substitutes that cover a range of lines. (See the next section for a particularly comprehensive example.) If the entire character sequence matched by the search pattern is needed in the replacement pattern, then the un-escaped character '&' can be used. On Line 29 above, try {:s/an e.ample/not &/^M}. If another line is to have the word 'not' prepended to a pattern, then '~' can save you from re-typing the replacement pattern. E.g. {:s/some pattern/~/^M} after the previous example would be equivalent to {:s/some pattern/not &/^M}. One other useful replacement pattern allows you to change the case of individual letters. The sequences {\u} and {\l} cause the immediately following character in the replacement to be converted to upper- or lower-case, respectively, if this character is a letter. The sequences {\U} and {\L} turn such conversion on, either until {\E} or {\e} is encountered, or until the end of the replacement pattern. For example, position the cursor on a line: pick a line, any line. Type {:s/.*/\U&/^M} and observe the result. You can undo it with {u}. The search pattern may actually match more than once on a single line. However, only the first pattern is substituted. If you would like ALL patterns matched on the line to be substituted, append a 'g' after the replacement pattern: {:s/123/456/g^M} will substitute EVERY occurrence on the line of 123 with 456. Section 37: advanced line addressing: {:p} {:g} {:v} Ex (available through the colon command in vi) offers several methods for specifying the lines on which a set of commands will act. For example, if you would like to see lines 50 through 100 of your file: {:50,100p^M} will display them, wait for you to [Hit return to continue], and leave you on line 100. Obviously, it would be easier just to do {100G} from within vi. But what if you would like to make changes to just those lines? Then the addressing is important and powerful. Line 30: This is a text. Line 31: Here is another text. Line 32: One more text line. The lines above contain a typing error that the author of this tutorial tends to make every time he attempts to type the word 'test'. To change all of these 'text's into 'test's, try the following: {:/^Line 30/,/^Line 32/s/text/test/^M}. This finds the beginning and end of the portion of text to be changed, and limits the substitution to each of the lines in that range. The {u} command applies to ALL of the substitutions as a group. This provides a mechanism for powerful text manipulations. And very complicated examples. Line 33: This test is a. Line 34: Here test is another. Line 35: One line more test. The above three lines have the second word out of order. The following command string will put things right. Be very careful when typing this: it is very long, full of special characters, and easy to mess up. You may want to consider reading the following section to understand it before trying the experiment. Don't worry about messing up the rest of the file, though: the address range is specified. {:/^Line 33/,/^Line 35/s/\([^:]*\): \([^ ]*\) \([^ ]*\) \([^.]*\)/\1: \2 \4 \3/^M} There are several things to note about this command string. First of all, the range of the substitute was limited by the address specification {/^Line 33/,/^Line 35/^M}. It might have been simpler to do {:set number^M} to see the line numbers directly, and then, in place of the two searches, typed the line numbers, e.g. {1396,1398}. Or to mark the lines with {ma} and {mb} and use {'a,'b}. Then follows the substitute pattern itself. To make it easier to understand what the substitute is doing, the command is duplicated below with the various patterns named for easier reference: s/\([^:]*\): \([^ ]*\) \([^ ]*\) \([^.]*\)/\1: \2 \4 \3/ |--\1---| |--\2---| |--\3---| |--\4---| |--------search pattern------------------|-replacement| |--pattern---| In overview, the substitute looks for a particular pattern made up of sub-patterns, which are named \1, \2, \3, and \4. These patterns are specified by stating what they are NOT. Pattern \1 is the sequence of characters that are NOT colons: in the search string, {[^:]} will match exactly one character that is not a colon, while appending the asterisk {[^:]*} specifies that the 'not a colon' pattern is to be repeated until no longer satisfied, and {\([^:]*\)} then gives the pattern its name, in this case \1. Outside of the specification of \1 comes {: }, specifying that the next two characters must be a colon followed by a blank. Patterns \2 and \3 are similar, specifying character sequences that are not blanks. Pattern \4 matches up to the period at the end of the line. The replacement pattern then consists of specifying the new order of the patterns. This is a particularly complicated example, perhaps the most complicated in this tutorial/reference. For our small examples, it is obviously tedious and error prone. For large files, however, it may be the most efficient way to make the desired modifications. (The reader is advised to look at the documentation for awk. This tool is very powerful and slightly simpler to use than vi for this kind of file manipulation. But, it is another command language to learn.) Many times, you will not want to operate on every line in a certain range. Rather you will want to make changes on lines that satisfy certain patterns; e.g. for every line that has the string 'NPS' on it, change 'NPS' to 'Naval Postgraduate School'. The {:g} addressing command was designed for this purpose. The example of this paragraph could be typed as {:g/NPS/s//Naval Postgraduate School/^M}. The general format of the command is {:g/(pattern)/cmds^M} and it works in the following way: all lines that match the pattern following the {:g} are 'tagged' in a special way. Then each of these lines have the commands following the pattern executed over them. Line 36: ABC rhino george farmer Dick jester lest Line 37: george farmer rhino lest jester ABC Line 38: rhino lest george Dick farmer ABC jester Type: {:g/^Line.*ABC/s/Dick/Harry Binswanger/|s/george farmer/gentleman george/p^M} There are several things of note here. First, lines 36, 37, and 38 above are tagged by the {:g}. Type {:g/^Line.*ABC/p^M} to verify this. Second, there are two substitutes on the same line separated by '|'. In general, any colon commands can be strung together with '|'. Third, both substitutes operate on all three lines, even though the first substitution works on only two of the lines (36 and 38). Fourth, the second substitute works on only two lines (36 and 37) and those are the two lines printed by the trailing 'p'. The {:v} command works similarly to the {:g} command, except that the sense of the test for 'tagging' the lines is reversed: all lines NOT matching the search pattern are tagged and operated on by the commands. Using {^V} to quote carriage return (see section 39) can be used in global substitutions to split two lines. For example, the command {:g/\. /s//.^V^M/g^M} will change your file so that each sentence is on a separate line. (Note that we have to 'escape' the '.', because '.' by itself matches any character. Our command says to find any line which contains a period followed by 2 spaces, and inserts a carriage return after the period.) Caveat: In some of the documentation for ex and vi you may find the comment to the effect that {\^M} can be used between commands following {:g}. The author of this tutorial has never gotten this to work and has crashed the editor trying. Section 38: higher level text objects and nroff: {(} {)} [{] [}] {[[} {]]} (Note: this section may be a little confusing because of our command notation. Using curly braces to surround command strings works fine as long as the command string does not contain any curly braces itself. However, the curly braces are legitimate commands in vi. Therefore, for any command sequence that contains curly braces, we will surround that sequence with SQUARE braces, as on the previous Section line.) In working with a document, particularly if using the text formatting programs nroff or troff, it is often advantageous to work in terms of sentences, paragraphs, and sections. The operations {(} and {)} move to the beginning of the previous and next sentences, respectively. Thus the command {d)} will delete the rest of the current sentence; likewise {d(} will delete the previous sentence if you are at the beginning of the current sentence, or, if you are not at the beginning of a sentence, it will delete the current sentence from the beginning up to where you are. A sentence is defined to end at a '.', '!', or '?' which is followed by either the end of a line, or by two spaces. Any number of closing ')', ']', '"', and ''' characters may appear after the '.', '!', or '?' before the spaces or end of line. Therefore, the {(} and {)} commands would recognize only one sentence in the following line, but two sentences on the second following line. Line 39: This is one sentence. Even though it looks like two. Line 40: This is two sentences. Because it has two spaces after the '.'. The operations [{] and [}] move over paragraphs and the operations {[[} and {]]} move over sections. A paragraph begins after each empty line, and also at each of a set of nroff paragraph macros. A section begins after each line with a form-feed ^L in the first column, and at each of a set of nroff section macros. When preparing a text file as input to nroff, you will probably be using a set of nroff macros to make the formatting specifications easier, or more to your taste. These macros are invoked by beginning a line with a period followed by the one or two letter macro name. Vi has been programmed to recognize these nroff macros, and if it doesn't recognize your particular macro you can use the {:set paragraphs} or {:set sections} commands so that it will. Section 39: more about inserting text There are a number of characters which you can use to make corrections during input mode. These are summarized in the following table. ^H deletes the last input character ^W deletes the last input word (erase) same as ^H; each terminal can define its own erase character; for some it is ^H, for others it is the DELETE key, and for others it is '@'. (kill) deletes the input on this line; each terminal can define its own line-kill character; for some it is ^U, for others it is '@'; you will need to experiment on your terminal to find out what your line-kill and erase characters are. \ escapes a following ^H, (kill), and (erase) characters: i.e. this is how to put these characters in your file. ^[ escape key; ends insertion mode ^? the delete key; interrupts an insertion, terminating it abnormally. ^M the return key; starts a new line. ^D back-tabs over the indentation set by the autoindent option 0^D back-tabs over all indentation back to the beginning of the line ^^D (up-arrow followed by control-d)same as 0^D, except the indentation will be restored at the beginning of the next line. ^V quotes the next non-printing character into the file If you wish to type in your erase or kill character (say # or @ or ^U) then you must precede it with a \, just as you would do at the normal system command level. A more general way of typing non-printing characters into the file is to precede them with a ^V. The ^V echoes as a ^ character on which the cursor rests. This indicates that the editor expects you to type a control character and it will be inserted into the file at that point. There are a few exceptions to note. The implementation of the editor does not allow the null character ^@ to appear in files. Also the linefeed character ^J is used by the editor to separate lines in the file, so it cannot appear in the middle of a line. (Trying to insert a ^M into a file, or putting it in the replacement part of a substitution string will result in the matched line being split in two. This, in effect, is how to split lines by using a substitution.) You can insert any other character, however, if you wait for the editor to echo the ^ before you type the character. In fact, the editor will treat a following letter as a request for the corresponding control character. This is the only way to type ^S or ^Q, since the system normally uses them to suspend and resume output and never gives them to the editor to process. If you are using the autoindent option you can back-tab over the indent which it supplies by typing a ^D. This backs up to the boundary specified by the shiftwidth option. This only works immediately after the supplied autoindent. When you are using the autoindent option you may wish to place a label at the left margin of a line. The way to do this easily is to type ^ (up-arrow) and then ^D. The editor will move the cursor to the left margin for one line, and restore the previous indent on the next. You can also type a 0 followed immediately by a ^D if you wish to kill all indentation and not have it resume on the next line. Section 40: more on operators: {d} {c} {<} {>} {!} {=} {y} Below is a non-exhaustive list of commands that can follow the operators to affect the range over which the operators will work. However, note that the operators {<}, {>}, {!}, and {=} do not operate on any object less than a line. Try {!w} and you will get a beep. To get the operator to work on just the current line, double it. E.g. {<<}. suffix will operate on ------ ------------------------ ^[ cancels the command w the word to the right of the cursor W ditto, but ignoring punctuation b the word to the left of the cursor B ditto, but ignoring punctuation e see below. E ditto (space) a character $ to the end of the line ^ to the beginning of the line / .. / up to, but not including, the string ? .. ? back to and including the string fc up to and including the occurrence of c Fc back to and including the occurrence of c tc up to but not including the occurrence of c Tc back to but not including the occurrence of c ^M TWO lines (that's right: two) (number)^M that many lines plus one (number)G up to and including line (number) ( the previous sentence if you are at the beginning of the current sentence, or the current sentence up to where you are if you are not at the beginning of the current sentence. Here, 'sentence' refers to the intuitive notion of an English sentence, ending with '!', '?', or '.' and followed by an end of line or two spaces. ) the rest of the current sentence { analogous to '(', but in reference to paragraphs: sections of text surrounded by blank lines } analogous to ')', but in reference to paragraphs [[ analogous to '(', but in reference to sections ]] analogous to ')', but in reference to sections H the first line on the screen M the middle line on the screen L the last line on the screen 3L through the third line from the bottom of the screen ^F forward a screenful ^B backward a screenful : : etc. etc. etc. This list is not exhaustive, but it should be sufficient to get the idea across: after the operator, you can specify a range with a move-the-cursor command, and that is the region of text over which the operator will be effective. Section 41: abbreviations: {:ab} When typing large documents you may find yourself typing a large phrase over and over. Vi gives you the ability to specify an abbreviation for a long string such that typing the abbreviation will automatically expand into the longer phrase. Type {:ab nps Naval Postgraduate School^M}. Now type: {iThis is to show off the nps's UNIX editor.^M^[} Section 42: vi's relationship with the ex editor: {:} Vi is actually one mode of editing within the editor ex. When you are running vi you can escape to the line oriented editor of ex by giving the command {Q}. All of the colon-commands which were introduced above are available in ex. Likewise, most ex commands can be invoked from vi using {:}. In rare instances, an internal error may occur in vi. In this case you will get a diagnostic and will be left in the command mode of ex. You can then save your work and quit if you wish by giving the command {x} after the colon prompt of ex. Or you can reenter vi (if you are brave) by giving ex the command {vi}. Section 43: vi on hard-copy terminals and dumb terminals: open mode (The author has not checked the following documentation for accuracy. It is abstracted from the Introduction to Vi Editing document.) If you are on a hard-copy terminal or a terminal which does not have a cursor which can move off the bottom line, you can still use the command set of vi, but in a different mode. When you give the vi command to UNIX, the editor will tell you that it is using open mode. This name comes from the open command in ex, which is used to get into the same mode. The only difference between visual mode (normal vi) and open mode is the way in which the text is displayed. In open mode the editor uses a single line window into the file, and moving backward and forward in the file causes new lines to be displayed, always below the current line. Two commands of vi work differently in open: {z} and {^R}. The {z} command does not take parameters, but rather draws a window of context around the current line and then returns you to the current line. If you are on a hard-copy terminal, the {^R} command will retype the current line. On such terminals, the editor normally uses two lines to represent the current line. The first line is a copy of the line as you started to edit it, and you work on the line below this line. When you delete characters, the editor types a number of \'s to show you the characters which are deleted. The editor also reprints the current line soon after such changes so that you can see what the line looks like again. It is sometimes useful to use this mode on very slow terminals which can support vi in the full screen mode. You can do this by entering ex and using an {open} command. ********************************************************************* Section 44: options: {:set} {setenv EXINIT} You will discover options as you need them. Do not worry about them very much on the first pass through this document. My advice is to glance through them, noting the ones that look interesting, ignoring the ones you don't understand, and try re-scanning them in a couple of weeks. If you decide that you have a favorite set of options and would like to change the default values for the editor, place a {setenv EXINIT} command in your .login file. When you are given an account under UNIX your directory has placed in it a file that is executed each time you log in. If one of the commands in this file sets the environment variable EXINIT to a string of vi commands, you can have many things done for you each time you invoke vi. For example, if you decide that you don't like tabstops placed every eight columns but prefer every four columns, and that you wish the editor to insert linefeeds for you when your typing gets you close to column 72, and you want auto-indentation, then include the following line in your .login file: setenv EXINIT='set tabstop=4 wrapmargin=8 autoindent' or equivalently setenv EXINIT='se ts=4 wm=8 ai' Each time you bring up vi, this command will be executed and the options set. There are forty options in the vi/ex editor that the user can set for his/her own convenience. They are described in more detail in individual sections below. The section line will show the full spelling of the option name, the abbreviation, and the default value of the option. The text itself comes from the ex reference manual and is not the epitome of clarity. Section 44.1: {autoindent}, {ai} default: noai Can be used to ease the preparation of structured program text. At the beginning of each append, change or insert command or when a new line is opened or created by an append, change, insert, or substitute operation within open or visual mode, ex looks at the line being appended after, the first line changed or the line inserted before and calculates the amount of white space at the start of the line. It then aligns the cursor at the level of indentation so determined. If the user then types lines of text in, they will continue to be justified at the displayed indenting level. If more white space is typed at the beginning of a line, the following line will start aligned with the first non-white character of the previous line. To back the cursor up to the preceding tab stop one can hit {^D}. The tab stops going backwards are defined at multiples of the shiftwidth option. You cannot backspace over the indent, except by sending an end-of-file with a {^D}. A line with no characters added to it turns into a completely blank line (the white space provided for the autoindent is discarded). Also specially processed in this mode are lines beginning with an up-arrow `^' and immediately followed by a {^D}. This causes the input to be repositioned at the beginning of the line, but retaining the previous indent for the next line. Similarly, a `0' followed by a {^D} repositions at the beginning but without retaining the previous indent. Autoindent doesn't happen in global commands or when the input is not a terminal. Section 44.2: {autoprint}, {ap} default: ap Causes the current line to be printed after each delete, copy, join, move, substitute, t, undo or shift command. This has the same effect as supplying a trailing `p' to each such command. Autoprint is suppressed in globals, and only applies to the last of many commands on a line. Section 44.3: {autowrite}, {aw} default: noaw Causes the contents of the buffer to be written to the current file if you have modified it and give a next, rewind, stop, tag, or {!} command, or a control- up-arrow {^^} (switch files) or {^]} (tag goto) command in visual. Note, that the edit and ex commands do not autowrite. In each case, there is an equivalent way of switching when autowrite is set to avoid the autowrite ({edit} for next, rewind! for rewind, stop! for stop, tag! for tag, shell for {!}, and {:e #} and a {:ta!} command from within visual). Section 44.4: {beautify}, {bf} default: nobeautify Causes all control characters except tab ^I, newline ^M and form-feed ^L to be discarded from the input. A complaint is registered the first time a backspace character is discarded. Beautify does not apply to command input. Section 44.5: {directory}, {dir} default: dir=/tmp Specifies the directory in which ex places its buffer file. If this directory in not writable, then the editor will exit abruptly when it fails to be able to create its buffer there. Section 44.6: {edcompatible} default: noedcompatible Causes the presence or absence of g and c suffixes on substitute commands to be remembered, and to be toggled by repeating the suffices. The suffix r makes the substitution be as in the {~} command, instead of like {&}. [Author's note: this should not concern users of vi.] Section 44.7: {errorbells}, {eb} default: noeb Error messages are preceded by a bell. However, bell ringing in open and visual modes on errors is not suppressed by setting noeb. If possible the editor always places the error message in a standout mode of the terminal (such as inverse video) instead of ringing the bell. Section 44.8: {hardtabs}, {ht} default: ht=8 Gives the boundaries on which terminal hardware tabs are set (or on which the system expands tabs). Section 44.9: {ignorecase}, {ic} default: noic All upper case characters in the text are mapped to lower case in regular expression matching. In addition, all upper case characters in regular expressions are mapped to lower case except in character class specifications (that is, character in square brackets). Section 44.10: {lisp} default: nolisp Autoindent indents appropriately for lisp code, and the {(}, {)}, [{], [}], {[[}, and {]]} commands in open and visual modes are modified in a straightforward, intuitive fashion to have meaning for lisp. [Author's note: but don't ask me to define them precisely.] Section 44.11: {list} default: nolist All printed lines will be displayed (more) unambiguously, showing tabs as ^I and end-of-lines with `$'. This is the same as in the ex command {list}. Section 44.12: {magic} default: magic for {ex} and {vi}, nomagic for edit. If nomagic is set, the number of regular expression meta-characters is greatly reduced, with only up-arrow `^' and `$' having special effects. In addition the meta-characters `~' and `&' of the replacement pattern are treated as normal characters. All the normal meta-characters may be made magic when nomagic is set by preceding them with a `\'. [Author's note: In other words, if magic is set a back-slant turns the magic off for the following character, and if nomagic is set a back-slant turns the magic ON for the following character. And, no, we are not playing Dungeons and Dragons, although I think the writers of these option notes must have played it all the time.] Section 44.13: {mesg} default: mesg Causes write permission to be turned off to the terminal while you are in visual mode, if nomesg is set. [Author's note: I don't know if anyone could have made any one sentence paragraph more confusing than this one. What it says is: mesg allows people to write to you even if you are in visual or open mode; nomesg locks your terminal so they can't write to you and mess up your screen.] Section 44.14: {number, nu} default: nonumber Causes all output lines to be printed with their line numbers. In addition each input line will be prompted with its line number. Section 44.15: {open} default: open If {noopen}, the commands open and visual are not permitted. This is set for edit to prevent confusion resulting from accidental entry to open or visual mode. [Author's note: As you may have guessed by now, there are actually three editors available under Berkeley UNIX that are in reality the same program, ex, with different options set: ex itself, vi, and edit.] Section 44.16: {optimize, opt} default: optimize Throughput of text is expedited by setting the terminal to not do automatic carriage returns when printing more than one (logical) line of output, greatly speeding output on terminals without addressable cursors when text with leading white space is printed. [Author's note: I still don't know what this option does.] Section 44.17: {paragraphs, para} default: para=IPLPPPQPP LIbp Specifies the paragraphs for the [{] and [}] operations in open and visual. The pairs of characters in the option's value are the names of the nroff macros which start paragraphs. Section 44.18: {prompt} default: prompt Command mode input is prompted for with a `:'. [Author's note: Doesn't seem to have any effect on vi.] Section 44.19: {readonly}, {ro} default: noro, unless invoked with -R or insufficient privileges on file This option allows you to guarantee that you won't clobber your file by accident. You can set the option and writes will fail unless you use an `!' after the write. Commands such as {x}, {ZZ}, the autowrite option, and in general anything that writes is affected. This option is turned on if you invoke the editor with the -R flag. Section 44.20: {redraw} default: noredraw The editor simulates (using great amounts of output), an intelligent terminal on a dumb terminal (e.g. during insertions in visual the characters to the right of the cursor position are refreshed as each input character is typed). Useful only at very high baud rates, and should be used only if the system is not heavily loaded: you will notice the performance degradation yourself. Section 44.21: {remap} default: remap If on, macros are repeatedly tried until they are unchanged. For example, if o is mapped to O, and O is mapped to I, then if remap is set, o will map to I, but if noremap is set, it will map to O . Section 44.22: {report} default: report=5 for ex and vi, 2 for edit Specifies a threshold for feedback from commands. Any command which modifies more than the specified number of lines will provide feedback as to the scope of its changes. For commands such as global, open, undo, and visual which have potentially more far reaching scope, the net change in the number of lines in the buffer is presented at the end of the command, subject to this same threshold. Thus notification is suppressed during a global command on the individual commands performed. Section 44.23: {scroll} default: scroll=1/2 window Determines the number of logical lines scrolled when a {^D} is received from a terminal in command mode, and determines the number of lines printed by a command mode z command (double the value of scroll). [Author's note: Doesn't seem to affect {^D} and {z} in visual (vi) mode.] Section 44.24: sections {sections} default: sections=SHNHH HU Specifies the section macros from nroff for the {[[} and {]]} operations in open and visual. The pairs of characters in the options's value are the names of the macros which start paragraphs. Section 44.25: {shell}, {sh} default: sh=/bin/sh Gives the path name of the shell forked for the shell escape command `!', and by the shell command. The default is taken from SHELL in the environment, if present. [Editor's note: I would suggest that you place the following line in your .login file: setenv SHELL '/bin/csh' ] Section 44.26: {shiftwidth}, {sw} default: sw=8 Used in reverse tabbing with {^D} when using autoindent to append text, and used by the shift commands. Should probably be the same value as the tabstop option. Section 44.27: {showmatch}, {sm} default: nosm In open and visual mode, when a `)' or `}' is typed, if the matching `(' or `{' is on the screen, move the cursor to it for one second. Extremely useful with complicated nested expressions, or with lisp. Section 44.28: {slowopen}, {slow} default: terminal dependent Affects the display algorithm used in visual mode, holding off display updating during input of new text to improve throughput when the terminal in use is both slow and unintelligent. See "An Introduction to Display Editing with Vi" for more details. Section 44.29: {tabstop}, {ts} default: ts=8 The editor expands tabs ^I to tabstop boundaries in the display. Section 44.30: {taglength}, {tl} default: tl=0 Tags are not significant beyond this many characters. A value of zero (the default) means that all characters are significant. Section 44.31: {tags} default: tags=tags /usr/lib/tags A path of files to be used as tag files for the tag command. A requested tag is searched for in the specified files, sequentially. By default files called tags are searched for in the current directory and in /usr/lib (a master file for the entire system). [Author's note: The author of this tutorial has never used this option, nor seen it used. I'm not even sure I know what they are talking about.] Section 44.32: {term} default: from environment variable TERM The terminal type of the output device. Section 44.33: {terse} default: noterse Shorter error diagnostics are produced for the experienced user. Section 44.34: {timeout} default: timeout Causes macros to time out after one second. Turn it off and they will wait forever. This is useful if you want multi-character macros, but if your terminal sends escape sequences for arrow keys, it will be necessary to hit escape twice to get a beep. [Editor's note: Another paragraph which requires a cryptographer.] Section 44.35: ttytype [Editor's note: I have found no documentation for this option at all.] Section 44.36: {warn} default: warn Warn if there has been `[No write since last change]' before a `!' command escape. Section 44.37: {window} default: window=speed dependent The number of lines in a text window in the visual command. The default is 8 at slow speeds (600 baud or less), 16 at medium speed (1200 baud), and the full screen (minus one line) at higher speeds. Section 44.38: {wrapscan}, {ws} default: ws Searches using the regular expressions in addressing will wrap around past the end of the file. Section 44.39: {wrapmargin}, {wm} default: wm=0 Defines a margin for automatic wrapover of text during input in open and visual modes. The numeric value is the number of columns from the right edge of the screen around which vi looks for a convenient place to insert a new-line character (wm=0 is OFF). This is very convenient for touch typists. Wrapmargin behaves much like fill/nojustify mode does in nroff. Section 44.40: {writeany}, {wa} default: nowa Inhibit the checks normally made before write commands, allowing a write to any file which the system protection mechanism will allow. Section 44.41: {w300}, {w1200}, {w9600} defaults: w300=8 w1200=16 w9600=full screen minus one These are not true options but set the default size of the window for when the speed is slow (300), medium (1200), or high (9600), respectively. They are suitable for an EXINIT and make it easy to change the 8/16/full screen rule. Section 45: Limitations Here are some editor limits that the user is likely to encounter: 1024 characters per line 256 characters per global command list 128 characters per file name 128 characters in the previous inserted and deleted text in open or visual 100 characters in a shell escape command 63 characters in a string valued option 30 characters in a tag name 250000 lines in the file (this is silently enforced). The visual implementation limits the number of macros defined with map to 32, and the total number of characters in macros to be less than 512. [Editor's note: These limits no longer apply to versions after 4.1BSD.] ================================================ FILE: docs/tutorial/vi.advanced.license ================================================ SPDX-License-Identifier: BSD-3-Clause Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/tutorial/vi.beginner ================================================ Section 1: {^F} {ZZ} To get out of this tutorial, type: ZZ (two capital Z's). Learning a new computer system implies learning a new text editor. These tutorial lessons were created by Dain Samples to help you come to grips with UC Berkeley's screen oriented editor called vi (for VIsual). This tutorial uses the vi editor itself as the means of presentation. For best use of this tutorial, read all of a screen before performing any of the indicated actions. This tutorial (or, at least, the first half of it) has been designed to systematically present the vi commands IF THE INSTRUCTIONS ARE FOLLOWED! If you are too adventuresome, you may find yourself lost. If you ever find yourself stuck, remember the first line of this section. OK, now find the control key on your keyboard; it usually has CTL or CTRL written on its upper surface. Your first assignment is to hold the control key down while you press the 'F' key on your keyboard. Please do so now. Section 2: {^F} {^B} Many of vi's commands use the control key and some other key in combination, as with the control and the 'F' key above. This is abbreviated CTL-F, or ^F. As you have probably guessed by now, ^F (CTL-F) moves you forward a fixed number of lines in the file. Throughout the remainder of the tutorial when you are ready to advance to the next section of text, hit ^F. The opposite command is ^B. Just for fun, you might want to try a ^B to see the previous section again. Be sure to do a ^F to return you here. Determine what the cursor looks like on your screen. Whatever it is (a box, an underscore, blinking, flashing, inverse, etc.) it should now be positioned in the upper left-hand corner of your screen under or on the S of Section. Become familiar with your cursor: to use vi correctly it is important to always know where the cursor is. Did you notice that when you do a ^F the cursor is left at the top of the screen, and a ^B leaves the cursor near the bottom of the screen? Try the two commands ^B^F again. And now do another ^F to see the next section. Section 3: {^F} {^B} You now have two basic commands for examining a file, both forwards (^F) and backwards (^B). Note that these are vi text editing commands: they are not commands for the tutorial. Indeed, this tutorial is nothing but a text file which you are now editing. Everything you do and learn in this tutorial will be applicable to editing text files. Therefore, when you are editing a file and are ready to see more of the text, entering ^F will get you to the next section of the file. Entering ^B will show you the previous section. Time for you to do another ^F. Section 4: {^F} {^B} {^M} (return key) We will adopt the notation of putting commands in curly braces so we can write them unambiguously. For example, if you are to type the command sequence "control B control F" (as we asked you to do above) it would appear as {^B^F}. This allows clear delineation of the command strings from the text. Remember that the curly braces are NOT part of the command string you are to type. Do NOT type the curly braces. Sometimes, the command string in the curly braces will be rather long, and may be such that the first couple of characters of the command will erase from the screen the string you are trying to read and type. It is suggested that you write down the longer commands BEFORE you type them so you won't forget them once they disappear. Now locate the return key on your keyboard: it is usually marked 'RETURN', indicate hitting the return key. In fact, the control-M key sequence is exactly the same as if you hit the return key, and vice versa. Now type {^F}. Section 5: {:q!} {ZZ} {^M} (return key) Recognize that this tutorial is nothing more than a text file that you are editing. This means that if you do something wrong, it is possible for you to destroy the information in this file. Don't worry. If this happens, type {ZZ} (two capital Z's) or {:q!^M} to leave the tutorial. Restart the tutorial. Once in the tutorial, you can then page forward with {^F} until you are back to where you want to be. (There are easier ways to do this, some of which will be discussed later, but this is the most straightforward.) You may want to write these commands down in a convenient place for quick reference: {:q!^M} and {ZZ} We will assume that you now know to do a {^F} to advance the file Section 6: {m} {G} {'} {z} Now that you know how to get around in the file via ^F and ^B let's look at other ways of examining a text file. Sometimes it is necessary, in the midst of editing a file, to examine another part of the file. You are then faced with the problem of remembering your place in the file, looking at the other text, and then getting back to your original location. Vi has a 'mark' command, m. Type {mp}. You have just 'marked' your current location in the file and given it the name 'p'. The command string below will do three things: position you at the beginning of the file (line 1), then return you to the location 'p' that you just marked with the 'm' command, and, since the screen will not look exactly the same as it does right now, the 'z' command will reposition the screen. (You may want to write the string down before typing it: once you type {1G} it will no longer be on the screen.) So now type {1G'pz^M} - a one followed by a capital G, followed by the quote mark, followed by a lower case 'p', then a lower case 'z', then a return (which is the same as a ^M). The {1G} moves you to line 1, i.e. the beginning of the file. The {'p} moves you to the location you marked with {mp}. The {z^M} command will repaint the screen putting the cursor at the top of the screen. (Now {^F}.) Section 7: {m} {G} {'} {z} Let's look at some variations on those commands. If you wanted to look at line 22 in the file and return to this location you could type {mp22G'p}. Do so now, observing that {22G} puts your cursor at the beginning of section 2 in the middle of the screen. Also note that, without the {z^M} command, the line with 'Section 7' on it is now in the MIDDLE of the screen, and not at the top. Our cursor is on the correct line (where we did the {mp} command) but the line is not where we might like it to be on the screen. That is the function of the {z^M} command. (Remember, ^M is the same as the 'return' key on your keyboard.) Type {z^M} now and observe the effect. As you can see, the 'Section 7' line is now at the top of the screen with the cursor happily under the capital S. If you would like the cursor line (i.e. the line which the cursor is on) in the middle of the screen again, you would type {z.}. If you wanted the cursor line to be at the BOTTOM of the screen, type {z-}. Try typing {z-z.z^M} and watch what happens. {^F} Section 8: {z} {m} {'} Note that the z command does not change the position of our cursor in the file itself, it simply moves the cursor around on the screen by moving the contents of the file around on the screen. The cursor stays on the same line of the file when using the z command. This brings up an important point. There are two questions that the users of vi continually need to know the answer to: "Where am I in the file?" and "Where am I on the screen?" The cursor on your terminal shows the answer to both questions. Some commands will move you around in the file, usually changing the location of the cursor on the screen as well. Other commands move the cursor around on the screen without changing your location in the file. Now type {ma}. Your location in the file has been given the name 'a'. If you type {'p'a} you will see the previous location we marked in section 7, and then will be returned to the current location. (You will want to do a {z^M} to repaint the screen afterwards.) Try it. {^F} Section 9: {m} {''} Now we can move about in our file pretty freely. By using the {m} command we can give the current cursor position a lower-case-character name, like 'p', 'a', 'e', 'm', or 'b'. Using the {G} command preceded by a line number we can look at any line in the file we like. Using the single quote command {'} followed by a character used in an {m} command, we can return to any location in the file we have marked. Try {m3}, {mM}, or {m$}. Not only lower-case letters are acceptable to the {m} and {'} commands: numbers, upper-case letters, and special characters are also acceptable. If you type the {'} command with a character that that has not been used in an {m} command, or for which the 'marked' text has been deleted, you will get a beep. Try {'i}. You should get a beep because the command {mi} has never been issued. (Unless you've been experimenting.) The command {''} attempts to return you to the location at which you last modified some part of your file. However, my experience has been that it is difficult to predict exactly where you will end up. Section 10: {^M} {-} Now do {ma}, marking your position at the top of the screen. Now hit {^M} (or return) until the cursor is right ... * <- here, over/under the asterisk. Now type {mb'a'b} and watch the cursor move from the asterisk to the top of the screen and back again. The {^M} command moves the cursor to the beginning of the next line. Now type {^M} until the cursor is right ... * <- here. The command to move the cursor to the beginning of the previous line is {-}. Practice moving the cursor around on the screen by using {^M} and {-}. BE CAREFUL to not move the cursor OFF the screen just yet. If you do, type {'az^M}. Now we can move to any line within the screen. Practice moving around in the file using the {^F}, {^B}, {-}, {^M}, {z}, and {'} commands. When you are fairly confident that you can get to where you need to be in the file, and position the cursor on the screen where you want it type {'az^M^F} (which, of course, moves you back to the beginning of this section, repositions the cursor at the top of the screen, and advances you to the next section). Section 11: scrolling: {^M} The cursor should now be on the S of 'Section 11', and this should be on the first line of the screen. If it is not, do {^M} or {-} as appropriate to put the cursor on the section line, and type {z^M}. Type {mc} to mark your place. Now type {^M} until the cursor is on the last line of this screen. Now do one more {^M} and observe the result. This is called scrolling. When you attempted to move to a line not displayed on the screen, the line at the top of the screen was 'scrolled off', and a line at the bottom of the screen was 'scrolled on'. The top line with 'Section 11' should no longer be visible. Now type {'cz^M} to reset the screen and type {^F} for the next section. Section 12: {-} {z} The {-} command moves the cursor to the previous line in the file. Now type {-}, which attempts to move the cursor to the previous line in this file. However, that line is not on the screen. The resulting action will depend on your terminal. (Do a {^Mz^M} to reposition the file). On intelligent terminals (e.g. VT100s, xterm, most modern terminals), a top line is 'scrolled on' and the bottom line is 'scrolled off'. Some very old terminals, however, may not have this 'reverse scrolling' feature. They will simply repaint the screen with the cursor line in the middle of the screen. On such terminals it is necessary to type {z^M} to get the cursor line back to the top of the screen. Section 13: Up until this point, the tutorial has always tried to make sure that the first line of each screen has on it the section number and a list of the commands covered in that section. This will no longer be strictly maintained. If you want the section line at the top of the screen, you now know enough commands to do it easily: do {^M} or {-} until the cursor is on the section line and then {z^M}. Also, from this point on, it may not be the case that a {^F} will put you at the beginning of the next section. Therefore, be aware of where you are in the file as we look at other commands. You may have to find your way back to a particular section without any help from the tutorial. If you do not feel comfortable with this, then it is suggested that you practice moving from section 1 to section 13, back and forth, using {^M}, {-}, {^F}, and {^B} commands for a while. Also make liberal use of the mark command {m}: if, for example, you make a habit of using {mz} to mark your current location in the file, then you will always be able to return to that location with {'z} if the editor does something strange and you have no idea where you are or what happened. And finally, the proscription against experimentation is hereby lifted: play with the editor. Feel free to try out variations on the commands and move around in the file. By this time you should be able to recover from any gross errors. Section 14: {^E} {^Y} {^D} {^U} Let us now look at a few other commands for moving around in the file, and moving the file around on the screen. Note that the commands we have already looked at are sufficient: you really don't need any more commands for looking in a file. The following commands are not absolutely necessary. However, they can make editing more convenient, and you should take note of their existence. But it would be perfectly valid to decide to ignore them on this first pass: you can learn them later when you see a need for them, if you ever do. First, let's clear up some potentially confusing language. In at least one place in the official document ('An Introduction to Display Editing with Vi' by William Joy, and Mark Horton, September 1980), the expression "to scroll down text" means that the cursor is moved down in your file. However, note that this may result in the text on the screen moving UP. This use of the word 'scroll' refers to the action of the cursor within the file. However, another legitimate use of the word refers to the action of the text on the screen. That is, if the lines on your screen move up toward the top of the screen, this would be 'scrolling the screen up'. If the lines move down toward the bottom of the screen, this would be referred to as scrolling down. I have tried to maintain the following jargon: 'scrolling' refers to what the text does on the screen, not to what the cursor does within the file. For the latter I will refer to the cursor 'moving', or to 'moving the cursor'. I realize that this is not necessarily consistent with Joy and Horton, but they were wrong. {^E} scrolls the whole screen up one line, keeping the cursor on the same line, if possible. However, if the cursor line is the first line on the screen, then the cursor is moved to the next line in the file. Try typing {^E}. {^Y} scrolls the screen down one line, keeping the cursor on the same line, if possible. However, if the cursor line is the last line on the screen, then the cursor is moved to the previous line in the file. Try it. {^D} moves the cursor down into the file, scrolling the screen up. {^U} moves the cursor up into the file, also scrolling the screen if the terminal you are on has the reverse scroll capability. Otherwise the screen is repainted. Note that {^E} and {^Y} move the cursor on the screen while trying to keep the cursor at the same place in the file (if possible: however, the cursor can never move off screen), while {^D} and {^U} keep the cursor at the same place on the screen while moving the cursor within the file. Section 15: {/ .. /^M} Another way to position yourself in the file is by giving the editor a string to search for. Type the following: {/Here 1/^M} and the cursor should end up right ...........................here ^. Now type {/Section 15:/^M} and the cursor will end up over/on .....................here ^. Now type {//^M} and observe that the cursor is now over the capital S five lines above this line. Typing {//^M} several more times will bounce the cursor back and forth between the two occurrences of the string. In other words, when you type a string between the two slashes, it is searched for. Typing the slashes with nothing between them acts as if you had typed the previous string again. Observe that the string you type between the two slashes is entered on the bottom line of the screen. Now type {/Search for x /^M} except replace the 'x' in the string with some other character, say 'b'. The message "Pattern not found" should appear on the bottom of the screen. If you hadn't replaced the 'x', then you would have found the string. Try it. Section 16: {? .. ?^M} {n} (search strings: ^ $) When you surround the sought-for string with slashes as in {/Search/}, the file is searched beginning from your current position in the file. If the string is not found by the end of the file, searching is restarted at the beginning of the file. However, if you do want the search to find the PREVIOUS rather than the NEXT occurrence of the string, surround the string with question marks instead of slash marks. Below are several occurrences of the same string. Here 2 Here 2 Here 2 Here 2 Here 2. Observe the effect of the following search commands (try them in the sequence shown): {/Here 2/^M} {//^M} {??^M} {/^Here 2/^M} {//^M} {??^M} {/Here 2$/^M} {//^M} {??^M} The first command looks for the next occurrence of the string 'Here 2'. However the second line of commands looks for an occurrence of 'Here 2' that is at the beginning of the line. When the caret (circumflex, up-arrow) is the first character of a search string it stands for the beginning of the line. When the dollar-sign is the last character of the search string it stands for the end of the line. Therefore, the third line of commands searches for the string only when it is at the end of the line. Since there is only one place the string begins a line, and only one place the string ends the line, subsequent {//^M} and {??^M} will find those same strings over and over. The {n} command will find the next occurrence of the / or ? search string. Try {/Here 2/^M} followed by several {n} and observe the effect. Then try {??^M} followed by several {n}. The {n} command remembers the direction of the last search. It is just a way to save a few keystrokes. Section 17: \ and magic-characters in search strings Now type {/Here 3$/^M}. You might expect the cursor to end up right......^ here. However, you will get "Pattern not found" at the bottom of the screen. Remember that the dollar-sign stands for the end of the line. Somehow, you must tell vi that you do not want the end of the line, but a dollar-sign. In other words, you must take away the special meaning that the dollar-sign has for the search mechanism. You do this (for any special character, including the caret ^) by putting a back-slash ('\', not '/') in front of the character. Now try {/Here 3\$/^M} and you should end up nine lines above this one. Try {//^M} and note that it returns you to the same place, and not to the first line of this paragraph: the back-slash character is not part of the search string and will not be found. To find the string in the first line of this paragraph, type {/Here 3\\\$/^M}. There are three back-slashes: the first takes away the special meaning from the second, and the third takes away the special meaning from the dollar-sign. Following is a list of the characters that have special meanings in search strings. If you wish to find a string containing one of these characters, you will have to precede the character with a backslash. These characters are called magic characters because of the fun and games you can have with them and they can have with you, if you aren't aware of what they do. ^ - (caret) beginning of a line $ - (dollar-sign) end of a line . - (period) matches any character \ - (backslash) the escape character itself [ - (square bracket) for finding patterns (see section #SEARCH) ] - (square bracket) ditto * - (asterisk) ditto Without trying to explain it here, note that {:set nomagic^M} turns off the special meanings of all but the ^ caret, $ dollar-sign, and backslash characters. Section 18: {: (colon commands)} {ZZ} In this section we will discuss getting into and out of the editor in more detail. If you are editing a file and wish to save the results the command sequence {:w^M} writes the current contents of the file out to disk, using the file name you used when you invoked the editor. That is, if you are at the command level in Unix, and you invoke vi with {vi foo} where foo is the name of the file you wish to edit, then foo is the name of the file used by the {:w^M} command. If you are done, the write and quit commands can be combined into a single command {:wq^M}. An even simpler way is the command {ZZ} (two capital Z's). If, for some reason, you wish to exit without saving any changes you have made, {:q!^M} does the trick. If you have not made any changes, the exclamation point is not necessary: {:q^M}. Vi is pretty good about not letting you get out without warning you that you haven't saved your file. We have mentioned before that you are currently in the vi editor, editing a file. If you wish to start the tutorial over from the very beginning, you could {:q!^M}, and then type {vi.tut beginner} or {vi vi.beginner} in response to the Unix prompt. This will provide an unmodified copy of this file for you, which might be necessary if you accidentally destroyed the copy you were working with. Just do a search for the last section you were in: e.g. {/Section 18:/^Mz^M}. Section 19: {H} {M} {L} Here are a few more commands that will move you around on the screen. Again, they are not absolutely necessary, but they can make screen positioning easier: {H} - puts the cursor at the top of the screen (the 'home' position) {M} - puts the cursor in the middle of the screen {L} - puts the cursor at the bottom of the screen. Try typing {HML} and watch the cursor. Try typing {5HM5L} and note that 5H puts you five lines from the top of the screen, and 5L puts you five lines from the bottom of the screen. Section 20: {w} {b} {0} {W} {B} {e} {E} {'} {`} Up to this point we have concentrated on positioning in the file, and positioning on the screen. Now let's look at positioning in a line. Put the cursor at the beginning of the following line and type {z^M}: This is a test line: your cursor should initially be at its beginning. The test line should now be at the top of your screen. Type {w} several times. Note that it moves you forward to the beginning of the next word. Now type {b} (back to the beginning of the word) several times till you are at the beginning of the line. (If you accidentally type too many {b}, type {w} until you are on the beginning of the line again.) Type {wwwww} (five w's) and note that the cursor is now on the colon in the sentence. The lower-case w command moves you forward one word, paying attention to certain characters such as colon and period as delimiters and counting them as words themselves. Now type {0} (zero, not o 'oh'): this moves you to the beginning of the current line. Now type {5w} and notice that this has the effect of repeating {w} five times and that you are now back on the colon. Type {0} (zero) again. To ignore the delimiters and to move to the beginning of the next word using only blanks, tabs and carriage-returns (these are called white-space characters) to delimit the words, use the {W} command: upper-case W. {B} takes you back a word using white-space characters as word delimiters. Note that the commands {wbWB} do not stop at the beginning or end of a line: they will continue to the next word on the next line in the direction specified (a blank line counts as a word). If you are interested in the END of the word, and not the BEGINNING, then use the {e} and {E} commands. These commands only move forward and there are no corresponding 'reverse search' commands for the end of a word. Also, we have been using the {'} command to move the cursor to a position that we have previously marked with the {m} command. However, position the cursor in the middle of a line (any line, just pick one) and type {mk}, marking that position with the letter k. Now type a few returns {^M} and type {'k}. Observe that the cursor is now at the beginning of the line that you marked. Now try {`k}: note that this is the reverse apostrophe, or back-quote, or grave accent, or whatever you want to call it. Also note that it moves you to the character that was marked, not just to the line that was marked. In addition, the {``} command works just like the {''} command except that you are taken to the exact character, not just to the line. (I'm still not sure which exact character, just as I'm still not sure which line.) Section 21: {l} {k} {j} {h} There are several commands to move around on the screen on a character by character basis: l - moves the cursor one character to the RIGHT k - moves the cursor UP one line j - moves the cursor DOWN one line h - moves the cursor one character to the LEFT Section 22: {i} {a} {I} {A} {o} {O} {^[} (escape key) For this and following sections you will need to use the ESCAPE key on your terminal. It is usually marked ESC. Since the escape key is the same as typing {^[} we will use ^[ for the escape key. Probably the most often used command in an editor is the insert command. Below are two lines of text, the first correct, the second incorrect. Position your cursor at the beginning of Line 1 and type {z^M}. Line 1: This is an example of the insert command. Line 2: This is an of the insert command. To make line 2 look like line 1, we are going to insert the characters 'example ' before the word 'of'. So, now move the cursor so that it is positioned on the 'o' of 'of'. (You can do this by typing {^M} to move to the beginning of line 2, followed by {6w} or {wwwwww} to position the cursor on the word 'of'.) Now carefully type the following string and observe the effects: {iexample ^[} (remember: ^[ is the escape key) The {i} begins the insert mode, and 'example ' is inserted into the line: be sure to notice the blank in 'example '. The {^[} ends insertion mode, and the line is updated to include the new string. Line 1 should look exactly like Line 2. Move the cursor to the beginning of Line 3 below and type {z^M}: Line 3: These lines are examples for the 'a' command. Line 4: These line are examples for the ' We will change line four to look like line three by using the append command. We need to append an 's' to the word 'line'. Position the cursor on the 'e' of 'line'. You can do this in several ways, one way is the following: First, type {/line /^M}. This puts us on the word 'line' in Line 4 (the blank in the search string is important!). Next, type {e}. The 'e' puts us at the end of the word. Now, type {as^[} (^[ is the escape character). The 'a' puts us in insert mode, AFTER the current character. We appended the 's', and the escape '^[' ended the insert mode. The difference between {i} (insert) and {a} (append) is that {i} begins inserting text BEFORE the cursor, and {a} begins inserting AFTER the cursor. Now type {Aa' command.^[}. The cursor is moved to the end of the line and the string following {A} is inserted into the text. Line 4 should now look like line 3. Just as {A} moves you to the end of the line to begin inserting, {I} would begin inserting at the FRONT of the line. To begin the insertion of a line after the cursor line, type {o}. To insert a line before the cursor line, type {O}. In other words {o123^[} is equivalent to {A^M123^[}, and {O123^[} is equivalent to {I123^M^[}. The text after the {o} or {O} is ended with an escape ^[. This paragraph contains information that is terminal dependent: you will just have to experiment to discover what your terminal does. Once in the insert mode, if you make a mistake in the typing, ^H will delete the previous character up to the beginning of the current insertion. ^W will delete the previous word, and one of ^U, @, or ^X will delete the current line (up to the beginning of the current insertion). You will need to experiment with ^U, @, and ^X to determine which works for your terminal. Section 23: {f} {x} {X} {w} {l} {r} {R} {s} {S} {J} Position the cursor at the beginning of line 5 and {z^M}: Line 5: The line as it should be. Line 6: The line as it shouldn't be. To make Line 6 like Line 5, we have to delete the 'n', the apostrophe, and the 't'. There are several ways to position ourselves at the 'n'. Choose whichever one suits your fancy: {/n't/^M} {^M7w6l} or {^M7w6 } (note the space) {^M3fn} (finds the 3rd 'n' on the line) Now {xxx} will delete the three characters, as will {3x}. Note that {X} deletes the character just BEFORE the cursor, as opposed to the character AT the cursor. Position the cursor at line 7 and {z^M}: Line 7: The line as it would be. Line 8: The line as it could be. To change line 8 into line 7 we need to change the 'c' in 'could' into a 'w'. The 'r' (replace) command was designed for this. Typing {rc} is the same as typing {xic^[} (i.e. delete the 'bad' character and insert the correct new character). Therefore, assuming that you have positioned the cursor on the 'c' of 'could', the easiest way to change 'could' into 'would' is {rw}. If you would like to now change the 'would' into 'should', use the substitute command, 's': {ssh^[}. The difference between 'r' and 's' is that 'r' (replace) replaces the current character with another character, while 's' (substitute) substitutes the current character with a string, ended with an escape. The capital letter version of replace {R} replaces each character by a character one at a time until you type an escape, ^[. The 'S' command substitutes the whole line. Position your cursor at the beginning of line 9 and {z^M}. Line 9: Love is a many splendored thing. Line 10: Love is a most splendored thing. To change line 10 into line 9, position the cursor at the beginning of 'most', and type {Rmany^[}. You may have noticed that, when inserting text, a new line is formed by typing {^M}. When changing, replacing, or substituting text you can make a new line by typing {^M}. However, neither {x} nor {X} will remove ^M to make two lines into one line. To do this, position the cursor on the first of the two lines you wish to make into a single line and type {J} (uppercase J for 'Join'). Section 24: {u} {U} Finally, before we review, let's look at the undo command. Position your cursor on line 11 below and {z^M}. Line 11: The quick brown fox jumped over the lazy hound dog. Line 12: the qwick black dog dumped over the laxy poune fox. Type the following set of commands, and observe carefully the effect of each of the commands: {/^Line 12:/^M} {ft} {rT} {fw} {ru} {w} {Rbrown fox^[} {w} {rj} {fx} {rz} {w} {Rhound dog^[} Line 12 now matches line 11. Now type {U} - capital 'U'. And line 12 now looks like it did before you typed in the command strings. Now type: {ft} {rT} {fw} {ru} {^M} {^M} and then type {u}: the cursor jumps back to the line containing the second change you made and 'undoes' it. That is, {U} 'undoes' all the changes on the line, and {u} 'undoes' only the last change. Type {u} several times and observe what happens: {u} can undo a previous {u}! Caveat: {U} only works as long as the cursor is still on the line. Move the cursor off the line and {U} will have no effect, except to possibly beep at you. However, {u} will undo the last change, no matter where it occurred. Section 25: review At this point, you have all the commands you need in order to make use of vi. The remainder of this tutorial will discuss variations on these commands as well as introduce new commands that make the job of editing more efficient. Here is a brief review of the basic commands we have covered. They are listed in the order of increasing complexity and/or decreasing necessity (to say that a command is less necessary is not to say that it is less useful!). These commands allow you to comfortably edit any text file. There are other commands that will make life easier but will require extra time to learn, obviously. You may want to consider setting this tutorial aside for several weeks and returning to it later after gaining experience with vi and getting comfortable with it. The convenience of some of the more exotic commands may then be apparent and worth the extra investment of time and effort required to master them. to get into the editor from Unix: {vi filename} to exit the editor saving all changes {ZZ} or {:wq^M} throwing away all changes {:q!^M} when no changes have been made {:q^M} save a file without exiting the editor {:w^M} write the file into another file {:w filename^M} insert text before the cursor {i ...text... ^[} at the beginning of the line {I ...text... ^[} after the cursor (append) {a ...text... ^[} at the end of the line {A ...text... ^[} after the current line {o ...text... ^[} before the current line {O ...text... ^[} delete the character ... under the cursor {x} to the left of the cursor {X} delete n characters {nx} or {nX} (for n a number) make two lines into one line (Join) {J} find a string in the file ... searching forward {/ ...string... /^M} searching backwards {? ...string... ?^M} repeat the last search command {n} repeat the last search command in the opposite direction {N} find the character c on this line ... searching forward {fc} searching backward {Fc} repeat the last 'find character' command {;} replace a character with character x {rx} substitute a single character with text {s ...text... ^[} substitute n characters with text {ns ...text... ^[} replace characters one-by-one with text {R ...text... ^[} undo all changes to the current line {U} undo the last single change {u} move forward in the file a "screenful" {^F} move back in the file a "screenful" {^B} move forward in the file one line {^M} or {+} move backward in the file one line {-} move to the beginning of the line {0} move to the end of the line {$} move forward one word {w} move forward one word, ignoring punctuation {W} move forward to the end of the next word {e} to the end of the word, ignoring punctuation{E} move backward one word {b} move back one word, ignoring punctuation {B} return to the last line modified {''} scroll a line onto the top of the screen {^Y} scroll a line onto the bottom of the screen {^E} move "up" in the file a half-screen {^U} move "down" in the file a half-screen {^D} move the cursor to the top screen line {H} move the cursor to the bottom screen line {L} move the cursor to the middle line {M} move LEFT one character position {h} or {^H} move RIGHT one character position {l} or { } move UP in the same column {k} or {^P} move DOWN in the same column {j} or {^N} mark the current position, name it x {mx} move to the line marked/named x {'x} move to the character position named x {`x} move to the beginning of the file {1G} move to the end of the file {G} move to line 23 in the file {23G} repaint the screen with the cursor line at the top of the screen {z^M} in the middle of the screen {z.} at the bottom of the screen {z-} More information on vi can be found in the file vi.advanced, which you can peruse at your leisure. From UNIX, type {vi.tut advanced^M} or {vi vi.advanced^M}. ================================================ FILE: docs/tutorial/vi.beginner.license ================================================ SPDX-License-Identifier: BSD-3-Clause Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 The Regents of the University of California Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 Keith Bostic Copyright (c) 2021-2024 Jeffrey H. Johnson ================================================ FILE: docs/tutorial/vi.tut.csh ================================================ #!/usr/bin/env csh # $OpenBSD: vi.tut.csh,v 1.2 2001/01/29 01:58:40 niklas Exp $ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 # The Regents of the University of California # Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 # Keith Bostic # Copyright (c) 2021-2024 Jeffrey H. Johnson # XXX I don't know what happens if there is .exrc file! # XXX Make sure that user is using a 24 line window!!! if ($1 != "beginner" && $1 != "advanced") then echo Usage: $0 beginner or $0 advanced exit endif # ($1 != "beginner" && $1 != "advanced") if ($?EXINIT) then set oexinit="$EXINIT" setenv EXINIT 'se ts=4 wm=8 sw=4' endif # $?EXINIT vi vi.{$1} onintr: if ($?oexinit) then setenv EXINIT "$oexinit" endif # $?oexinit ================================================ FILE: ex/ex.awk ================================================ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 1980, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994 # The Regents of the University of California # Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000 # Keith Bostic # Copyright (c) 2021-2024 Jeffrey H. Johnson BEGIN { printf("enum {"); first = 1; } /^\/\* C_[0-9A-Z_]* \*\// { printf("%s\n\t%s%s", first ? "" : ",", $2, first ? " = 0" : ""); first = 0; next; } END { printf("\n};\n"); } ================================================ FILE: ex/ex.c ================================================ /* $OpenBSD: ex.c,v 1.23 2023/06/23 15:06:45 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #if defined(DEBUG) && defined(COMLOG) static void ex_comlog(SCR *, EXCMD *); #endif /* if defined(DEBUG) && defined(COMLOG) */ static EXCMDLIST const * ex_comm_search(char *, size_t); static int ex_discard(SCR *); static int ex_line(SCR *, EXCMD *, MARK *, int *, int *); static int ex_load(SCR *); static void ex_unknown(SCR *, char *, size_t); /* * ex -- * Main ex loop. * * PUBLIC: int ex(SCR **); */ int ex(SCR **spp) { GS *gp; MSGS *mp; SCR *sp; TEXT *tp; u_int32_t flags; sp = *spp; gp = sp->gp; /* Start the ex screen. */ if (ex_init(sp)) return (1); /* Flush any saved messages. */ while ((mp = LIST_FIRST(&gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); LIST_REMOVE(mp, q); free(mp->buf); free(mp); } /* If reading from a file, errors should have name and line info. */ if (F_ISSET(gp, G_SCRIPTED)) { gp->excmd.if_lno = 1; gp->excmd.if_name = "script"; } /* * !!! * Initialize the text flags. The beautify edit option historically * applied to ex command input read from a file. In addition, the * first time a ^H was discarded from the input, there was a message, * "^H discarded", that was displayed. We don't bother. */ LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR); for (;; ++gp->excmd.if_lno) { /* Display status line and flush. */ if (F_ISSET(sp, SC_STATUS)) { if (!F_ISSET(sp, SC_EX_SILENT)) msgq_status(sp, sp->lno, 0); F_CLR(sp, SC_STATUS); } (void)ex_fflush(sp); /* Set the flags the user can reset. */ if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_PROMPT)) LF_SET(TXT_PROMPT); /* Clear any current interrupts, and get a command. */ CLR_INTERRUPT(sp); if (ex_txt(sp, &sp->tiq, ':', flags)) return (1); if (INTERRUPTED(sp)) { (void)ex_puts(sp, "\n"); (void)ex_fflush(sp); continue; } /* Initialize the command structure. */ CLEAR_EX_PARSER(&gp->excmd); /* * If the user entered a single carriage return, send * ex_cmd() a separator -- it discards single newlines. */ tp = TAILQ_FIRST(&sp->tiq); if (tp->len == 0) { gp->excmd.cp = " "; /* __TK__ why not |? */ gp->excmd.clen = 1; } else { gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; } F_INIT(&gp->excmd, E_NRSEP); if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED)) return (1); if (INTERRUPTED(sp)) { CLR_INTERRUPT(sp); msgq(sp, M_ERR, "Interrupted"); } /* Sync recovery if changes were made. */ if (F_ISSET(sp->ep, F_RCV_SYNC)) rcv_sync(sp, 0); /* * If the last command caused a restart, or switched screens * or into vi, return. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) { *spp = sp; break; } /* If the last command switched files, we don't care. */ F_CLR(sp, SC_FSWITCH); /* * If we're exiting this screen, move to the next one. By * definition, this means returning into vi, so return to the * main editor loop. The ordering is careful, don't discard * the contents of sp until the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) return (1); *spp = screen_next(sp); if (*spp) { F_CLR(*spp, SC_SCR_VI); F_SET(*spp, SC_SCR_EX); } return (screen_end(sp)); } } return (0); } /* * ex_cmd -- * The guts of the ex parser: parse and execute a string containing * ex commands. * * !!! * This code MODIFIES the string that gets passed in, to delete quoting * characters, etc. The string cannot be readonly/text space, nor should * you expect to use it again after ex_cmd() returns. * * !!! * For the fun of it, if you want to see if a vi clone got the ex argument * parsing right, try: * * echo 'foo|bar' > file1; echo 'foo/bar' > file2; * vi * :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq * * or: vi * :set|file|append|set|file * * For extra credit, try them in a startup .exrc file. * * PUBLIC: int ex_cmd(SCR *); */ int ex_cmd(SCR *sp) { enum nresult nret; EX_PRIVATE *exp; EXCMD *ecp; GS *gp; MARK cur; recno_t lno; size_t arg1_len, discard, len; u_int32_t flags; long ltmp; int at_found, gv_found; int ch, cnt, delim, isaddr, namelen; int newscreen, notempty, tmp, vi_address; char *arg1, *p, *s, *t; gp = sp->gp; exp = EXP(sp); /* * We always start running the command on the top of the stack. * This means that *everything* must be resolved when we leave * this function for any reason. */ loop: ecp = LIST_FIRST(&gp->ecq); /* If we're reading a command from a file, set up error information. */ if (ecp->if_name != NULL) { gp->if_lno = ecp->if_lno; gp->if_name = ecp->if_name; } /* * If a move to the end of the file is scheduled for this command, * do it now. */ if (F_ISSET(ecp, E_MOVETOEND)) { if (db_last(sp, &sp->lno)) goto rfail; sp->cno = 0; F_CLR(ecp, E_MOVETOEND); } /* If we found a newline, increment the count now. */ if (F_ISSET(ecp, E_NEWLINE)) { ++gp->if_lno; ++ecp->if_lno; F_CLR(ecp, E_NEWLINE); } /* (Re)initialize the EXCMD structure, preserving some flags. */ CLEAR_EX_CMD(ecp); /* Initialize the argument structures. */ if (argv_init(sp, ecp)) goto err; /* Initialize +cmd, saved command information. */ arg1 = NULL; ecp->save_cmdlen = 0; /* Skip s, empty lines. */ for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen) if ((ch = *ecp->cp) == '\n') { ++gp->if_lno; ++ecp->if_lno; } else if (isblank(ch)) notempty = 1; else break; /* * !!! * Permit extra colons at the start of the line. Historically, * ex/vi allowed a single extra one. It's simpler not to count. * The stripping is done here because, historically, any command * could have preceding colons, e.g. ":g/pattern/:p" worked. */ if (ecp->clen != 0 && ch == ':') { notempty = 1; while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':'); } /* * Command lines that start with a double-quote are comments. * * !!! * Historically, there was no escape or delimiter for a comment, e.g. * :"foo|set was a single comment and nothing was output. Since nvi * permits users to escape characters into command lines, we * have to check for that case. */ if (ecp->clen != 0 && ch == '"') { while (--ecp->clen > 0 && *++ecp->cp != '\n'); if (*ecp->cp == '\n') { F_SET(ecp, E_NEWLINE); ++ecp->cp; --ecp->clen; } goto loop; } /* Skip whitespace. */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!isblank(ch)) break; } /* * The last point at which an empty line can mean do nothing. * * !!! * Historically, in ex mode, lines containing only characters * were the same as a single , i.e. a default command. * In vi mode, they were ignored. In .exrc files this was a serious * annoyance, as vi kept trying to treat them as print commands. We * ignore backward compatibility in this case, discarding lines that * contain only characters from .exrc files. * * !!! * This is where you end up when you're done a command, i.e. clen has * gone to zero. Continue if there are more commands to run. */ if (ecp->clen == 0 && (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) { if (ex_load(sp)) goto rfail; ecp = LIST_FIRST(&gp->ecq); if (ecp->clen == 0) goto rsuccess; goto loop; } /* * Check to see if this is a command for which we may want to move * the cursor back up to the previous line. (The command :1 * wants a separator, but the command : wants to erase * the command line.) If the line is empty except for s, * or , we'll probably want to move up. I * don't think there's any way to get characters *after* the * command character, but this is the ex parser, and I've been wrong * before. */ if (F_ISSET(ecp, E_NRSEP) && ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004')) F_CLR(ecp, E_NRSEP); /* Parse command addresses. */ if (ex_range(sp, ecp, &tmp)) goto rfail; if (tmp) goto err; /* * Skip s and any more colons (the command :3,5:print * worked, historically). */ for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { ch = *ecp->cp; if (!isblank(ch) && ch != ':') break; } /* * If no command, ex does the last specified of p, l, or #, and vi * moves to the line. Otherwise, determine the length of the command * name by looking for the first non-alphabetic character. (There * are a few non-alphabetic characters in command names, but they're * all single character commands.) This isn't a great test, because * it means that, for the command ":e +cut.c file", we'll report that * the command "cut" wasn't known. However, it makes ":e+35 file" work * correctly. * * !!! * Historically, lines with multiple adjacent (or separated) * command separators were very strange. For example, the command * |||, when the cursor was on line 1, displayed * lines 2, 3 and 5 of the file. In addition, the command " | " * would only display the line after the next line, instead of the * next two lines. No ideas why. It worked reasonably when executed * from vi mode, and displayed lines 2, 3, and 4, so we do a default * command for each separator. */ #define SINGLE_CHAR_COMMANDS "\004!#&*<=>@~" newscreen = 0; if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\n') { if (strchr(SINGLE_CHAR_COMMANDS, *ecp->cp)) { p = ecp->cp; ++ecp->cp; --ecp->clen; namelen = 1; } else { for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!isalpha(*ecp->cp)) break; if ((namelen = ecp->cp - p) == 0) { msgq(sp, M_ERR, "Unknown command name"); goto err; } } /* * !!! * Historic vi permitted flags to immediately follow any * subset of the 'delete' command, but then did not permit * further arguments (flag, buffer, count). Make it work. * Permit further arguments for the few shreds of dignity * it offers. * * Adding commands that start with 'd', and match "delete" * up to a l, p, +, - or # character can break this code. * * !!! * Capital letters beginning the command names ex, edit, * next, previous, tag and visual (in vi mode) indicate the * command should happen in a new screen. */ switch (p[0]) { case 'd': for (s = p, t = cmds[C_DELETE].name; *s == *t; ++s, ++t); if (s[0] == 'l' || s[0] == 'p' || s[0] == '+' || s[0] == '-' || s[0] == '^' || s[0] == '#') { len = (ecp->cp - p) - (s - p); ecp->cp -= len; ecp->clen += len; ecp->rcmd = cmds[C_DELETE]; ecp->rcmd.syntax = "1bca1"; ecp->cmd = &ecp->rcmd; goto skip_srch; } break; case 'E': case 'F': case 'N': case 'P': case 'T': case 'V': newscreen = 1; p[0] = tolower(p[0]); break; } /* * Search the table for the command. * * !!! * Historic vi permitted the mark to immediately follow the * 'k' in the 'k' command. Make it work. * * !!! * Historic vi permitted any flag to follow the s command, e.g. * "s/e/E/|s|sgc3p" was legal. Make the command "sgc" work. * Since the following characters all have to be flags, i.e. * alphabetics, we can let the s command routine return errors * if it was some illegal command string. This code will break * if an "sg" or similar command is ever added. The substitute * code doesn't care if it's a "cgr" flag or a "#lp" flag that * follows the 's', but we limit the choices here to "cgr" so * that we get unknown command messages for wrong combinations. */ if ((ecp->cmd = ex_comm_search(p, namelen)) == NULL) switch (p[0]) { case 'k': if (namelen == 2) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->cmd = &cmds[C_K]; break; } goto unknown; case 's': for (s = p + 1, cnt = namelen; --cnt; ++s) if (s[0] != 'c' && s[0] != 'g' && s[0] != 'r') break; if (cnt == 0) { ecp->cp -= namelen - 1; ecp->clen += namelen - 1; ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; break; } /* FALLTHROUGH */ default: unknown: if (newscreen) p[0] = toupper(p[0]); if (sp != NULL && p != NULL && namelen > 1) ex_unknown(sp, p, namelen); goto err; } /* * The visual command has a different syntax when called * from ex than when called from a vi colon command. FMH. * Make the change now, before we test for the newscreen * semantic, so that we're testing the right one. */ skip_srch: if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI)) ecp->cmd = &cmds[C_VISUAL_VI]; /* * !!! * Historic vi permitted a capital 'P' at the beginning of * any command that started with 'p'. Probably wanted the * P[rint] command for backward compatibility, and the code * just made Preserve and Put work by accident. Nvi uses * Previous to mean previous-in-a-new-screen, so be careful. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) && (ecp->cmd == &cmds[C_PRINT] || ecp->cmd == &cmds[C_PRESERVE])) newscreen = 0; /* Test for a newscreen associated with this command. */ if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN)) goto unknown; /* Secure means no shell access. */ if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, ecp->cmd->name, EXM_SECURE); goto err; } /* * Multiple < and > characters; another "feature". Note, * The string passed to the underlying function may not be * NULL terminated in this case. */ if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') || (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) { for (ch = *p; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (*ecp->cp != ch) break; if (argv_exp0(sp, ecp, p, ecp->cp - p)) goto err; } /* Set the format style flags for the next command. */ if (ecp->cmd == &cmds[C_HASH]) exp->fdef = E_C_HASH; else if (ecp->cmd == &cmds[C_LIST]) exp->fdef = E_C_LIST; else if (ecp->cmd == &cmds[C_PRINT]) exp->fdef = E_C_PRINT; F_CLR(ecp, E_USELASTCMD); } else { /* Print is the default command. */ ecp->cmd = &cmds[C_PRINT]; /* Set the saved format flags. */ F_SET(ecp, exp->fdef); /* * !!! * If no address was specified, and it's not a global command, * we up the address by one. (I have no idea why globals are * exempted, but it's (ahem) historic practice.) */ if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) { ecp->addrcnt = 1; ecp->addr1.lno = sp->lno + 1; ecp->addr1.cno = sp->cno; } F_SET(ecp, E_USELASTCMD); } /* * !!! * Historically, the number option applied to both ex and vi. One * strangeness was that ex didn't switch display formats until a * command was entered, e.g. 's after the set didn't change to * the new format, but :1p would. */ if (O_ISSET(sp, O_NUMBER)) { F_SET(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); } else F_CLR(ecp, E_OPTNUM); /* Check for ex mode legality. */ if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) { msgq(sp, M_ERR, "%s: command not available in ex mode", ecp->cmd->name); goto err; } /* Add standard command flags. */ F_SET(ecp, ecp->cmd->flags); if (!newscreen) F_CLR(ecp, E_NEWSCREEN); /* * There are three normal termination cases for an ex command. They * are the end of the string (ecp->clen), or unescaped (by characters) or '|' characters. As we're now past * possible addresses, we can determine how long the command is, so we * don't have to look for all the possible terminations. Naturally, * there are some exciting special cases: * * 1: The bang, global, v and the filter versions of the read and * write commands are delimited by s (they can contain * shell pipes). * 2: The ex, edit, next and visual in vi mode commands all take ex * commands as their first arguments. * 3: The s command takes an RE as its first argument, and wants it * to be specially delimited. * * Historically, '|' characters in the first argument of the ex, edit, * next, vi visual, and s commands didn't delimit the command. And, * in the filter cases for read and write, and the bang, global and v * commands, they did not delimit the command at all. * * For example, the following commands were legal: * * :edit +25|s/abc/ABC/ file.c * :s/|/PIPE/ * :read !spell % | columnate * :global/pattern/p|l * * It's not quite as simple as it sounds, however. The command: * * :s/a/b/|s/c/d|set * * was also legal, i.e. the historic ex parser (using the word loosely, * since "parser" implies some regularity of syntax) delimited the RE's * based on its delimiter and not anything so irretrievably vulgar as a * command syntax. * * Anyhow, the following code makes this all work. First, for the * special cases we move past their special argument(s). Then, we * do normal command processing on whatever is left. Barf-O-Rama. */ discard = 0; /* Characters discarded from the command. */ arg1_len = 0; ecp->save_cmd = ecp->cp; if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] || ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &cmds[C_VISUAL_VI]) { /* * Move to the next non-whitespace character. A '!' * immediately following the command is eaten as a * force flag. */ if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); /* Reset, don't reparse. */ ecp->save_cmd = ecp->cp; } for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!isblank(*ecp->cp)) break; /* * QUOTING NOTE: * * The historic implementation ignored all escape characters * so there was no way to put a space or newline into the +cmd * field. We do a simplistic job of fixing it by moving to the * first whitespace character that isn't escaped. The escaping * characters are stripped as no longer useful. */ if (ecp->clen > 0 && *ecp->cp == '+') { ++ecp->cp; --ecp->clen; for (arg1 = p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { ++discard; --ecp->clen; ch = *++ecp->cp; } else if (isblank(ch)) break; *p++ = ch; } arg1_len = ecp->cp - arg1; /* Reset, so the first argument isn't reparsed. */ ecp->save_cmd = ecp->cp; } } else if (ecp->cmd == &cmds[C_BANG] || ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) { /* * QUOTING NOTE: * * We use backslashes to escape characters, although * this wasn't historic practice for the bang command. It was * for the global and v commands, and it's common usage when * doing text insert during the command. Escaping characters * are stripped as no longer useful. */ for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (ch == '\\' && ecp->clen > 1 && ecp->cp[1] == '\n') { ++discard; --ecp->clen; ch = *++ecp->cp; ++gp->if_lno; ++ecp->if_lno; } else if (ch == '\n') break; *p++ = ch; } } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) { /* * For write commands, if the next character is a , and * the next non-blank character is a '!', it's a filter command * and we want to eat everything up to the . For read * commands, if the next non-blank character is a '!', it's a * filter command and we want to eat everything up to the next * . Otherwise, we're done. */ for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (isblank(ch)) tmp = 1; else break; } if (ecp->clen > 0 && ch == '!' && (ecp->cmd == &cmds[C_READ] || tmp)) for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\n') break; } else if (ecp->cmd == &cmds[C_SUBSTITUTE]) { /* * Move to the next non-whitespace character, we'll use it as * the delimiter. If the character isn't an alphanumeric or * a '|', it's the delimiter, so parse it. Otherwise, we're * into something like ":s g", so use the special s command. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!isblank(ecp->cp[0])) break; if (isalnum(ecp->cp[0]) || ecp->cp[0] == '|') { ecp->rcmd = cmds[C_SUBSTITUTE]; ecp->rcmd.fn = ex_subagain; ecp->cmd = &ecp->rcmd; } else if (ecp->clen > 0) { /* * QUOTING NOTE: * * Backslashes quote delimiter characters for RE's. * The backslashes are NOT removed since they'll be * used by the RE code. Move to the third delimiter * that's not escaped (or the end of the command). */ delim = *ecp->cp; ++ecp->cp; --ecp->clen; for (cnt = 2; ecp->clen > 0 && cnt != 0; --ecp->clen, ++ecp->cp) if (ecp->cp[0] == '\\' && ecp->clen > 1) { ++ecp->cp; --ecp->clen; } else if (ecp->cp[0] == delim) --cnt; } } /* * Use normal quoting and termination rules to find the end of this * command. * * QUOTING NOTE: * * Historically, vi permitted ^V's to escape 's in the .exrc * file. It was almost certainly a bug, but that's what bug-for-bug * compatibility means, Grasshopper. Also, ^V's escape the command * delimiters. Literal next quote characters in front of the newlines, * '|' characters or literal next characters are stripped as they're * no longer useful. */ vi_address = ecp->clen != 0 && ecp->cp[0] != '\n'; for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = ecp->cp[0]; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { tmp = ecp->cp[1]; if (tmp == '\n' || tmp == '|') { if (tmp == '\n') { ++gp->if_lno; ++ecp->if_lno; } ++discard; --ecp->clen; ++ecp->cp; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') F_SET(ecp, E_NEWLINE); --ecp->clen; break; } *p++ = ch; } /* * Save off the next command information, go back to the * original start of the command. */ p = ecp->cp + 1; ecp->cp = ecp->save_cmd; ecp->save_cmd = p; ecp->save_cmdlen = ecp->clen; ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard; /* * QUOTING NOTE: * * The "set tags" command historically used a backslash, not the * user's literal next character, to escape whitespace. Handle * it here instead of complicating the argv_exp3() code. Note, * this isn't a particularly complex trap, and if backslashes were * legal in set commands, this would have to be much more complicated. */ if (ecp->cmd == &cmds[C_SET]) for (p = ecp->cp, len = ecp->clen; len > 0; --len, ++p) if (*p == '\\') *p = CH_LITERAL; /* * Set the default addresses. It's an error to specify an address for * a command that doesn't take them. If two addresses are specified * for a command that only takes one, lose the first one. Two special * cases here, some commands take 0 or 2 addresses. For most of them * (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines. * * Also, if the file is empty, some commands want to use an address of * 0, i.e. the entire file is 0 to 0, and the default first address is * 0. Otherwise, an entire file is 1 to N and the default line is 1. * Note, we also add the E_ADDR_ZERO flag to the command flags, for the * case where the 0 address is only valid if it's a default address. * * Also, set a flag if we set the default addresses. Some commands * (ex: z) care if the user specified an address or if we just used * the current cursor. */ switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 1; F_SET(ecp, E_ADDR_DEF); if (F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = sp->lno; } else ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ ecp->addrcnt = 1; ecp->addr1 = ecp->addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ if (ecp->addrcnt == 0) /* Default to nothing. */ break; goto two_addr; case E_ADDR2_ALL: /* Zero/two addresses: */ if (ecp->addrcnt == 0) { /* Default entire/empty file. */ F_SET(ecp, E_ADDR_DEF); ecp->addrcnt = 2; if (sp->ep == NULL) ecp->addr2.lno = 0; else if (db_last(sp, &ecp->addr2.lno)) goto err; if (F_ISSET(ecp, E_ADDR_ZERODEF) && ecp->addr2.lno == 0) { ecp->addr1.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = 1; ecp->addr1.cno = ecp->addr2.cno = 0; F_SET(ecp, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */ two_addr: switch (ecp->addrcnt) { case 0: /* Default cursor/empty file. */ ecp->addrcnt = 2; F_SET(ecp, E_ADDR_DEF); if (sp->lno == 1 && F_ISSET(ecp, E_ADDR_ZERODEF)) { if (db_last(sp, &lno)) goto err; if (lno == 0) { ecp->addr1.lno = ecp->addr2.lno = 0; F_SET(ecp, E_ADDR_ZERO); } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; } else ecp->addr1.lno = ecp->addr2.lno = sp->lno; ecp->addr1.cno = ecp->addr2.cno = sp->cno; break; case 1: /* Default to first address. */ ecp->addr2 = ecp->addr1; break; case 2: break; } break; default: if (ecp->addrcnt) /* Error. */ goto usage; } /* * !!! * The ^D scroll command historically scrolled the value of the scroll * option or to EOF. It was an error if the cursor was already at EOF. * (Leading addresses were permitted, but were then ignored.) */ if (ecp->cmd == &cmds[C_SCROLL]) { ecp->addrcnt = 2; ecp->addr1.lno = sp->lno + 1; ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); ecp->addr1.cno = ecp->addr2.cno = sp->cno; if (db_last(sp, &lno)) goto err; if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno) ecp->addr2.lno = lno; } ecp->flagoff = 0; for (p = ecp->cmd->syntax; *p != '\0'; ++p) { /* * The force flag is sensitive to leading whitespace, i.e. * "next !" is different from "next!". Handle it before * skipping leading s. */ if (*p == '!') { if (ecp->clen > 0 && *ecp->cp == '!') { ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_FORCE); } continue; } /* Skip leading s. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) if (!isblank(*ecp->cp)) break; if (ecp->clen == 0) break; switch (*p) { case '1': /* +, -, #, l, p */ /* * !!! * Historically, some flags were ignored depending * on where they occurred in the command line. For * example, in the command, ":3+++p--#", historic vi * acted on the '#' flag, but ignored the '-' flags. * It's unambiguous what the flags mean, so we just * handle them regardless of the stupidity of their * location. */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '+': ++ecp->flagoff; break; case '-': case '^': --ecp->flagoff; break; case '#': F_CLR(ecp, E_OPTNUM); FL_SET(ecp->iflags, E_C_HASH); exp->fdef |= E_C_HASH; break; case 'l': FL_SET(ecp->iflags, E_C_LIST); exp->fdef |= E_C_LIST; break; case 'p': FL_SET(ecp->iflags, E_C_PRINT); exp->fdef |= E_C_PRINT; break; default: goto end_case1; } end_case1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ for (; ecp->clen; --ecp->clen, ++ecp->cp) switch (*ecp->cp) { case '-': FL_SET(ecp->iflags, E_C_DASH); break; case '.': FL_SET(ecp->iflags, E_C_DOT); break; case '+': FL_SET(ecp->iflags, E_C_PLUS); break; case '^': FL_SET(ecp->iflags, E_C_CARAT); break; case '=': if (*p == '3') { FL_SET(ecp->iflags, E_C_EQUAL); break; } /* FALLTHROUGH */ default: goto end_case23; } end_case23: break; case 'b': /* buffer */ /* * !!! * Historically, "d #" was a delete with a flag, not a * delete into the '#' buffer. If the current command * permits a flag, don't use one as a buffer. However, * the 'l' and 'p' flags were legal buffer names in the * historic ex, and were used as buffers, not flags. */ if ((ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^' || ecp->cp[0] == '#') && strchr(p, '1') != NULL) break; /* * !!! * Digits can't be buffer names in ex commands, or the * command "d2" would be a delete into buffer '2', and * not a two-line deletion. */ if (!isdigit(ecp->cp[0])) { ecp->buffer = *ecp->cp; ++ecp->cp; --ecp->clen; FL_SET(ecp->iflags, E_C_BUFFER); } break; case 'c': /* count [01+a] */ ++p; /* Validate any signed value. */ if (!isdigit(*ecp->cp) && (*p != '+' || (*ecp->cp != '+' && *ecp->cp != '-'))) break; /* If a signed value, set appropriate flags. */ if (*ecp->cp == '-') FL_SET(ecp->iflags, E_C_COUNT_NEG); else if (*ecp->cp == '+') FL_SET(ecp->iflags, E_C_COUNT_POS); if ((nret = nget_slong(<mp, ecp->cp, &t, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); goto err; } if (ltmp == 0 && *p != '0') { msgq(sp, M_ERR, "Count may not be zero"); goto err; } ecp->clen -= (t - ecp->cp); ecp->cp = t; /* * Counts as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * * Set a count flag; some underlying commands (see * join) do different things with counts than with * line addresses. */ if (*p == 'a') { ecp->addr1 = ecp->addr2; ecp->addr2.lno = ecp->addr1.lno + ltmp - 1; } else ecp->count = ltmp; FL_SET(ecp->iflags, E_C_COUNT); break; case 'f': /* file */ if (argv_exp2(sp, ecp, ecp->cp, ecp->clen)) goto err; goto arg_cnt_chk; case 'l': /* line */ /* * Get a line specification. * * If the line was a search expression, we may have * changed state during the call, and we're now * searching the file. Push ourselves onto the state * stack. */ if (ex_line(sp, ecp, &cur, &isaddr, &tmp)) goto rfail; if (tmp) goto err; /* Line specifications are always required. */ if (!isaddr) { msgq_str(sp, M_ERR, ecp->cp, "%s: bad line specification"); goto err; } /* * The target line should exist for these commands, * but 0 is legal for them as well. */ if (cur.lno != 0 && !db_exist(sp, cur.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } ecp->lineno = cur.lno; break; case 'S': /* string, file exp. */ if (ecp->clen != 0) { if (argv_exp1(sp, ecp, ecp->cp, ecp->clen, ecp->cmd == &cmds[C_BANG])) goto err; goto addr_verify; } /* FALLTHROUGH */ case 's': /* string */ if (argv_exp0(sp, ecp, ecp->cp, ecp->clen)) goto err; goto addr_verify; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following * character. Quoting characters are stripped here * since they are no longer useful. * * First there was the word. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p++ = *++ecp->cp; } else if (isblank(ch)) { ++ecp->cp; --ecp->clen; break; } else *p++ = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; /* Delete intervening whitespace. */ for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) { ch = *ecp->cp; if (!isblank(ch)) break; } if (ecp->clen == 0) goto usage; /* Followed by the string. */ for (p = t = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp, ++p) { ch = *ecp->cp; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; *p = *++ecp->cp; } else *p = ch; } if (argv_exp0(sp, ecp, t, p - t)) goto err; goto addr_verify; case 'w': /* word */ if (argv_exp3(sp, ecp, ecp->cp, ecp->clen)) goto err; arg_cnt_chk: if (*++p != 'N') { /* N */ /* * If a number is specified, must either be * 0 or that number, if optional, and that * number, if required. */ tmp = *p - '0'; if ((*++p != 'o' || exp->argsoff != 0) && exp->argsoff != tmp) goto usage; } goto addr_verify; default: msgq(sp, M_ERR, "Internal syntax table error (%s: %s)", ecp->cmd->name, KEY_NAME(sp, *p)); } } /* Skip trailing whitespace. */ for (; ecp->clen > 0; --ecp->clen) { ch = *ecp->cp++; if (!isblank(ch)) break; } /* * There shouldn't be anything left, and no more required fields, * i.e neither 'l' or 'r' in the syntax string. */ if (ecp->clen != 0 || strpbrk(p, "lr")) { usage: msgq(sp, M_ERR, "Usage: %s", ecp->cmd->usage); goto err; } /* * Verify that the addresses are legal. Check the addresses here, * because this is a place where all ex addresses pass through. * (They don't all pass through ex_line(), for instance.) We're * assuming that any non-existent line doesn't exist because it's * past the end-of-file. That's a pretty good guess. * * If it's a "default vi command", an address of zero is okay. */ addr_verify: switch (ecp->addrcnt) { case 2: /* * Historic ex/vi permitted commands with counts to go past * EOF. So, for example, if the file only had 5 lines, the * ex command "1,6>" would fail, but the command ">300" * would succeed. Since we don't want to have to make all * of the underlying commands handle random line numbers, * fix it here. */ if (ecp->addr2.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } } else if (!db_exist(sp, ecp->addr2.lno)) { if (FL_ISSET(ecp->iflags, E_C_COUNT)) { if (db_last(sp, &lno)) goto err; ecp->addr2.lno = lno; } else { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } } /* FALLTHROUGH */ case 1: if (ecp->addr1.lno == 0) { if (!F_ISSET(ecp, E_ADDR_ZERO) && (F_ISSET(sp, SC_EX) || !F_ISSET(ecp, E_USELASTCMD))) { ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); goto err; } } else if (!db_exist(sp, ecp->addr1.lno)) { ex_badaddr(sp, NULL, A_EOF, NUM_OK); goto err; } break; } /* * If doing a default command and there's nothing left on the line, * vi just moves to the line. For example, ":3" and ":'a,'b" just * move to line 3 and line 'b, respectively, but ":3|" prints line 3. * * !!! * In addition, IF THE LINE CHANGES, move to the first nonblank of * the line. * * !!! * This is done before the absolute mark gets set; historically, * "/a/,/b/" did NOT set vi's absolute mark, but "/a/,/b/d" did. */ if ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) && F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) { switch (ecp->addrcnt) { case 2: if (sp->lno != (ecp->addr2.lno ? ecp->addr2.lno : 1)) { sp->lno = ecp->addr2.lno ? ecp->addr2.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; case 1: if (sp->lno != (ecp->addr1.lno ? ecp->addr1.lno : 1)) { sp->lno = ecp->addr1.lno ? ecp->addr1.lno : 1; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } break; } ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; goto loop; } /* * Set the absolute mark -- we have to set it for vi here, in case * it's a compound command, e.g. ":5p|6" should set the absolute * mark for vi. */ if (F_ISSET(ecp, E_ABSMARK)) { cur.lno = sp->lno; cur.cno = sp->cno; F_CLR(ecp, E_ABSMARK); if (mark_set(sp, ABSMARK1, &cur, 1)) goto err; } #if defined(DEBUG) && defined(COMLOG) ex_comlog(sp, ecp); #endif /* if defined(DEBUG) && defined(COMLOG) */ /* Increment the command count if not called from vi. */ if (F_ISSET(sp, SC_EX)) ++sp->ccnt; /* * If file state available, and not doing a global command, * log the start of an action. */ if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL)) (void)log_cursor(sp); /* * !!! * There are two special commands for the purposes of this code: the * default command () or the scrolling commands (^D * and ) as the first non- characters in the line. * * If this is the first command in the command line, we received the * command from the ex command loop and we're talking to a tty, and * and there's nothing else on the command line, and it's one of the * special commands, we move back up to the previous line, and erase * the prompt character with the output. Since ex runs in canonical * mode, we don't have to do anything else, a has already * been echoed by the tty driver. It's OK if vi calls us -- we won't * be in ex mode so we'll do nothing. */ if (F_ISSET(ecp, E_NRSEP)) { if (sp->ep != NULL && F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) && (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL])) gp->scr_ex_adjust(sp, EX_TERM_SCROLL); F_CLR(ecp, E_NRSEP); } /* * Call the underlying function for the ex command. * * XXX * Interrupts behave like errors, for now. */ if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { if (F_ISSET(gp, G_SCRIPTED)) F_SET(sp, SC_EXIT_FORCE); goto err; } #ifdef DEBUG /* Make sure no function left global temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "%s: temporary buffer not released", ecp->cmd->name); } #endif /* ifdef DEBUG */ /* * Ex displayed the number of lines modified immediately after each * command, so the command "1,10d|1,10d" would display: * * 10 lines deleted * 10 lines deleted * * * Executing ex commands from vi only reported the final modified * lines message -- that's wrong enough that we don't match it. */ if (F_ISSET(sp, SC_EX)) mod_rpt(sp); /* * Integrate any offset parsed by the underlying command, and make * sure the referenced line exists. * * XXX * May not match historic practice (which I've never been able to * completely figure out.) For example, the '=' command from vi * mode often got the offset wrong, and complained it was too large, * but didn't seem to have a problem with the cursor. If anyone * complains, ask them how it's supposed to work, they might know. */ if (sp->ep != NULL && ecp->flagoff) { if (ecp->flagoff < 0) { if (sp->lno <= -ecp->flagoff) { msgq(sp, M_ERR, "Flag offset to before line 1"); goto err; } } else { if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); goto err; } if (!db_exist(sp, sp->lno + ecp->flagoff)) { msgq(sp, M_ERR, "Flag offset past end-of-file"); goto err; } } sp->lno += ecp->flagoff; } /* * If the command executed successfully, we may want to display a line * based on the autoprint option or an explicit print flag. (Make sure * that there's a line to display.) Also, the autoprint edit option is * turned off for the duration of global commands. */ if (F_ISSET(sp, SC_EX) && sp->ep != NULL && sp->lno != 0) { /* * The print commands have already handled the `print' flags. * If so, clear them. */ if (FL_ISSET(ecp->iflags, E_CLRFLAG)) FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT); /* If hash set only because of the number option, discard it. */ if (F_ISSET(ecp, E_OPTNUM)) FL_CLR(ecp->iflags, E_C_HASH); /* * If there was an explicit flag to display the new cursor line, * or autoprint is set and a change was made, display the line. * If any print flags were set use them, else default to print. */ LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)); if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) && !F_ISSET(sp, SC_EX_GLOBAL) && O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) { /* Honor the number option if autoprint is set. */ if (F_ISSET(ecp, E_OPTNUM)) LF_INIT(E_C_HASH); else LF_INIT(E_C_PRINT); } if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) { cur.lno = sp->lno; cur.cno = 0; (void)ex_print(sp, ecp, &cur, &cur, flags); } } /* * If the command had an associated "+cmd", it has to be executed * before we finish executing any more of this ex command. For * example, consider a .exrc file that contains the following lines: * * :set all * :edit +25 file.c|s/abc/ABC/|1 * :3,5 print * * This can happen more than once -- the historic vi simply hung or * dropped core, of course. Prepend the + command back into the * current command and continue. We may have to add an additional * character. We know that it will fit because we * discarded at least one space and the + character. */ if (arg1_len != 0) { /* * If the last character of the + command was a * character, it would be treated differently because of the * append. Quote it, if necessary. */ if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) { *--ecp->save_cmd = CH_LITERAL; ++ecp->save_cmdlen; } ecp->save_cmd -= arg1_len; ecp->save_cmdlen += arg1_len; memmove(ecp->save_cmd, arg1, arg1_len); /* * Any commands executed from a +cmd are executed starting at * the first column of the last line of the file -- NOT the * first nonblank.) The main file startup code doesn't know * that a +cmd was set, however, so it may have put us at the * top of the file. (Note, this is safe because we must have * switched files to get here.) */ F_SET(ecp, E_MOVETOEND); } /* Update the current command. */ ecp->cp = ecp->save_cmd; ecp->clen = ecp->save_cmdlen; /* * !!! * If we've changed screens or underlying files, any pending global or * v command, or @ buffer that has associated addresses, has to be * discarded. This is historic practice for globals, and necessary for * @ buffers that had associated addresses. * * Otherwise, if we've changed underlying files, it's not a problem, * we continue with the rest of the ex command(s), operating on the * new file. However, if we switch screens (either by exiting or by * an explicit command), we have no way of knowing where to put output * messages, and, since we don't control screens here, we could screw * up the upper layers, (e.g. we could exit/reenter a screen multiple * times). So, return and continue after we've got a new screen. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) { at_found = gv_found = 0; LIST_FOREACH(ecp, &sp->gp->ecq, q) switch (ecp->agv_flags) { case 0: case AGV_AT_NORANGE: break; case AGV_AT: if (!at_found) { at_found = 1; msgq(sp, M_ERR, "@ with range running when the file/screen changed"); } break; case AGV_GLOBAL: case AGV_V: if (!gv_found) { gv_found = 1; msgq(sp, M_ERR, "Global/v command running when the file/screen changed"); } break; default: abort(); } if (at_found || gv_found) goto discard; if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH)) goto rsuccess; } goto loop; /* NOTREACHED */ err: /* * On command failure, we discard keys and pending commands remaining, * as well as any keys that were mapped and waiting. The save_cmdlen * test is not necessarily correct. If we fail early enough we don't * know if the entire string was a single command or not. Guess, as * it's useful to know if commands other than the current one are being * discarded. */ if (ecp->save_cmdlen == 0) for (; ecp->clen; --ecp->clen) { ch = *ecp->cp++; if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { --ecp->clen; ++ecp->cp; } else if (ch == '\n' || ch == '|') { if (ecp->clen > 1) ecp->save_cmdlen = 1; break; } } if (ecp->save_cmdlen != 0 || LIST_FIRST(&gp->ecq) != &gp->excmd) { discard: msgq(sp, M_BERR, "Ex command failed: pending commands discarded"); ex_discard(sp); } if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "Ex command failed: mapped keys discarded"); rfail: tmp = 1; if (0) rsuccess: tmp = 0; /* Turn off any file name error information. */ gp->if_name = NULL; /* Turn off the global bit. */ F_CLR(sp, SC_EX_GLOBAL); return (tmp); } /* * ex_range -- * Get a line range for ex commands, or perform a vi ex address search. * * PUBLIC: int ex_range(SCR *, EXCMD *, int *); */ int ex_range(SCR *sp, EXCMD *ecp, int *errp) { enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr; MARK m; int isaddr; *errp = 0; /* * Parse comma or semi-colon delimited line specs. * * Semi-colon delimiters update the current address to be the last * address. For example, the command * * :3;/pattern/ecp->cp * * will search for pattern from line 3. In addition, if ecp->cp * is not a valid command, the current line will be left at 3, not * at the original address. * * Extra addresses are discarded, starting with the first. * * !!! * If any addresses are missing, they default to the current line. * This was historically true for both leading and trailing comma * delimited addresses as well as for trailing semicolon delimited * addresses. For consistency, we make it true for leading semicolon * addresses as well. */ for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;) switch (*ecp->cp) { case '%': /* Entire file. */ /* Vi ex address searches didn't permit % signs. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* It's an error if the file is empty. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } /* * !!! * A percent character addresses all of the lines in * the file. Historically, it couldn't be followed by * any other address. We do it as a text substitution * for simplicity. POSIX 1003.2 is expected to follow * this practice. * * If it's an empty file, the first line is 0, not 1. */ if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } if (db_last(sp, &ecp->addr2.lno)) return (1); ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1; ecp->addr1.cno = ecp->addr2.cno = 0; ecp->addrcnt = 2; addr = ADDR_FOUND; ++ecp->cp; --ecp->clen; break; case ',': /* Comma delimiter. */ /* Vi ex address searches didn't permit commas. */ if (F_ISSET(ecp, E_VISEARCH)) goto ret; /* FALLTHROUGH */ case ';': /* Semi-colon delimiter. */ if (sp->ep == NULL) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } if (addr != ADDR_FOUND) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (*ecp->cp == ';') switch (ecp->addrcnt) { case 0: abort(); /* NOTREACHED */ case 1: sp->lno = ecp->addr1.lno; sp->cno = ecp->addr1.cno; break; case 2: sp->lno = ecp->addr2.lno; sp->cno = ecp->addr2.cno; break; } addr = ADDR_NEED; /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ ++ecp->cp; --ecp->clen; break; default: /* Get a line specification. */ if (ex_line(sp, ecp, &m, &isaddr, errp)) return (1); if (*errp) return (0); if (!isaddr) goto ret; if (addr == ADDR_FOUND) { ex_badaddr(sp, NULL, A_COMBO, NUM_OK); *errp = 1; return (0); } switch (ecp->addrcnt) { case 0: ecp->addr1 = m; ecp->addrcnt = 1; break; case 1: ecp->addr2 = m; ecp->addrcnt = 2; break; case 2: ecp->addr1 = ecp->addr2; ecp->addr2 = m; break; } addr = ADDR_FOUND; break; } /* * !!! * Vi ex address searches are indifferent to order or trailing * semi-colons. */ ret: if (F_ISSET(ecp, E_VISEARCH)) return (0); if (addr == ADDR_NEED) switch (ecp->addrcnt) { case 0: ecp->addr1.lno = sp->lno; ecp->addr1.cno = sp->cno; ecp->addrcnt = 1; break; case 2: ecp->addr1 = ecp->addr2; /* FALLTHROUGH */ case 1: ecp->addr2.lno = sp->lno; ecp->addr2.cno = sp->cno; ecp->addrcnt = 2; break; } if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) { msgq(sp, M_ERR, "The second address is smaller than the first"); *errp = 1; } return (0); } /* * ex_line -- * Get a single line address specifier. * * The way the "previous context" mark worked was that any "non-relative" * motion set it. While ex/vi wasn't totally consistent about this, ANY * numeric address, search pattern, '$', or mark reference in an address * was considered non-relative, and set the value. Which should explain * why we're hacking marks down here. The problem was that the mark was * only set if the command was called, i.e. we have to set a flag and test * it later. * * XXX * This is probably still not exactly historic practice, although I think * it's fairly close. */ static int ex_line(SCR *sp, EXCMD *ecp, MARK *mp, int *isaddrp, int *errp) { enum nresult nret; long total, val; int isneg; int (*sf)(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int); char *endp; *isaddrp = *errp = 0; F_CLR(ecp, E_DELTA); /* No addresses permitted until a file has been read in. */ if (sp->ep == NULL && strchr("$0123456789'\\/?.+-^", *ecp->cp)) { ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); *errp = 1; return (0); } switch (*ecp->cp) { case '$': /* Last line in the file. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); mp->cno = 0; if (db_last(sp, &mp->lno)) return (1); ++ecp->cp; --ecp->clen; break; /* Absolute line number. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *isaddrp = 1; F_SET(ecp, E_ABSMARK); if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } if (!NPFITS(MAX_REC_NUMBER, 0, val)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno = val; mp->cno = 0; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; break; case '\'': /* Use a mark. */ *isaddrp = 1; F_SET(ecp, E_ABSMARK); if (ecp->clen == 1) { msgq(sp, M_ERR, "No mark name supplied"); *errp = 1; return (0); } if (mark_get(sp, ecp->cp[1], mp, M_ERR)) { *errp = 1; return (0); } ecp->cp += 2; ecp->clen -= 2; break; case '\\': /* Search: forward/backward. */ /* * !!! * I can't find any difference between // and \/ or between * ?? and \?. Mark Horton doesn't remember there being any * difference. C'est la vie. */ if (ecp->clen < 2 || (ecp->cp[1] != '/' && ecp->cp[1] != '?')) { msgq(sp, M_ERR, "\\ not followed by / or ?"); *errp = 1; return (0); } ++ecp->cp; --ecp->clen; sf = ecp->cp[0] == '/' ? f_search : b_search; goto search; case '/': /* Search forward. */ sf = f_search; goto search; case '?': /* Search backward. */ sf = b_search; search: mp->lno = sp->lno; mp->cno = sp->cno; if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp, SEARCH_MSG | SEARCH_PARSE | SEARCH_SET | (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) { *errp = 1; return (0); } /* Fix up the command pointers. */ ecp->clen -= (endp - ecp->cp); ecp->cp = endp; *isaddrp = 1; F_SET(ecp, E_ABSMARK); break; case '.': /* Current position. */ *isaddrp = 1; mp->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { if (db_last(sp, &mp->lno)) return (1); if (mp->lno != 0) mp->lno = 1; } else mp->lno = sp->lno; /* * !!! * Historically, . was the same as .+, i.e. * the '+' could be omitted. (This feature is found in ed * as well.) */ if (ecp->clen > 1 && isdigit(ecp->cp[1])) *ecp->cp = '+'; else { ++ecp->cp; --ecp->clen; } break; } /* Skip trailing s. */ for (; ecp->clen > 0 && isblank(ecp->cp[0]); ++ecp->cp, --ecp->clen); /* * Evaluate any offset. If no address yet found, the offset * is relative to ".". */ total = 0; if (ecp->clen != 0 && (isdigit(ecp->cp[0]) || ecp->cp[0] == '+' || ecp->cp[0] == '-' || ecp->cp[0] == '^')) { if (!*isaddrp) { *isaddrp = 1; mp->lno = sp->lno; mp->cno = sp->cno; } /* * Evaluate an offset, defined as: * * [+-^]*[]*[0-9]* * * The rough translation is any number of signs, optionally * followed by numbers, or a number by itself, all * separated. * * !!! * All address offsets were additive, e.g. "2 2 3p" was the * same as "7p", or, "/ZZZ/ 2" was the same as "/ZZZ/+2". * Note, however, "2 /ZZZ/" was an error. It was also legal * to insert signs without numbers, so "3 - 2" was legal, and * equal to 4. * * !!! * Offsets were historically permitted for any line address, * e.g. the command "1,2 copy 2 2 2 2" copied lines 1,2 after * line 8. * * !!! * Offsets were historically permitted for search commands, * and handled as addresses: "/pattern/2 2 2" was legal, and * referenced the 6th line after pattern. */ F_SET(ecp, E_DELTA); for (;;) { for (; ecp->clen > 0 && isblank(ecp->cp[0]); ++ecp->cp, --ecp->clen); if (ecp->clen == 0 || (!isdigit(ecp->cp[0]) && ecp->cp[0] != '+' && ecp->cp[0] != '-' && ecp->cp[0] != '^')) break; if (!isdigit(ecp->cp[0]) && !isdigit(ecp->cp[1])) { total += ecp->cp[0] == '+' ? 1 : -1; --ecp->clen; ++ecp->cp; } else { if (ecp->cp[0] == '-' || ecp->cp[0] == '^') { ++ecp->cp; --ecp->clen; isneg = 1; } else isneg = 0; /* Get a signed long, add it to the total. */ if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK || (nret = NADD_SLONG(total, val)) != NUM_OK) { ex_badaddr(sp, NULL, A_NOTSET, nret); *errp = 1; return (0); } total += isneg ? -val : val; ecp->clen -= (endp - ecp->cp); ecp->cp = endp; } } } /* * Any value less than 0 is an error. Make sure that the new value * will fit into a recno_t. */ if (*isaddrp && total != 0) { if (total < 0) { if (-total > mp->lno) { msgq(sp, M_ERR, "Reference to a line number less than 0"); *errp = 1; return (0); } } else if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) { ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); *errp = 1; return (0); } mp->lno += total; } return (0); } /* * ex_load -- * Load up the next command, which may be an @ buffer or global command. */ static int ex_load(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; F_CLR(sp, SC_EX_GLOBAL); /* * Lose any exhausted commands. We know that the first command * can't be an AGV command, which makes things a bit easier. */ for (gp = sp->gp;;) { /* * If we're back to the original structure, leave it around, * but discard any allocated source name, we've returned to * the beginning of the command stack. */ if ((ecp = LIST_FIRST(&gp->ecq)) == &gp->excmd) { if (F_ISSET(ecp, E_NAMEDISCARD)) { free(ecp->if_name); ecp->if_name = NULL; } return (0); } /* * ecp->clen will be 0 for the first discarded command, but * may not be 0 for subsequent ones, e.g. if the original * command was ":g/xx/@a|s/b/c/", then when we discard the * command pushed on the stack by the @a, we have to resume * the global command which included the substitute command. */ if (ecp->clen != 0) return (0); /* * If it's an @, global or v command, we may need to continue * the command on a different line. */ if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { /* Discard any exhausted ranges. */ while ((rp = TAILQ_FIRST(&ecp->rq))) { if (rp->start > rp->stop) { TAILQ_REMOVE(&ecp->rq, rp, q); free(rp); } else break; } /* If there's another range, continue with it. */ if (rp) break; /* If it's a global/v command, fix up the last line. */ if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) { if (db_exist(sp, ecp->range_lno)) sp->lno = ecp->range_lno; else { if (db_last(sp, &sp->lno)) return (1); if (sp->lno == 0) sp->lno = 1; } } free(ecp->o_cp); } /* Discard the EXCMD. */ LIST_REMOVE(ecp, q); free(ecp); } /* * We only get here if it's an active @, global or v command. Set * the current line number, and get a new copy of the command for * the parser. Note, the original pointer almost certainly moved, * so we have play games. */ ecp->cp = ecp->o_cp; memcpy(ecp->cp, ecp->cp + ecp->o_clen, ecp->o_clen); ecp->clen = ecp->o_clen; ecp->range_lno = sp->lno = rp->start++; if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V)) F_SET(sp, SC_EX_GLOBAL); return (0); } /* * ex_discard -- * Discard any pending ex commands. */ static int ex_discard(SCR *sp) { GS *gp; EXCMD *ecp; RANGE *rp; /* * We know the first command can't be an AGV command, so we don't * process it specially. We do, however, nail the command itself. */ for (gp = sp->gp; (ecp = LIST_FIRST(&gp->ecq)) != &gp->excmd;) { if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { while ((rp = TAILQ_FIRST(&ecp->rq))) { TAILQ_REMOVE(&ecp->rq, rp, q); free(rp); } free(ecp->o_cp); } LIST_REMOVE(ecp, q); free(ecp); } LIST_FIRST(&gp->ecq)->clen = 0; return (0); } /* * ex_unknown -- * Display an unknown command name. */ static void ex_unknown(SCR *sp, char *cmd, size_t len) { size_t blen; char *bp; GET_SPACE_GOTO(sp, bp, blen, len + 1); bp[len] = '\0'; memcpy(bp, cmd, len); msgq_str(sp, M_ERR, bp, "The %s command is unknown"); FREE_SPACE(sp, bp, blen); alloc_err: return; } /* * ex_is_abbrev - * The vi text input routine needs to know if ex thinks this is an * [un]abbreviate command, so it can turn off abbreviations. See * the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine. * * PUBLIC: int ex_is_abbrev(char *, size_t); */ int ex_is_abbrev(char *name, size_t len) { EXCMDLIST const *cp; return ((cp = ex_comm_search(name, len)) != NULL && (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE])); } /* * ex_is_unmap - * The vi text input routine needs to know if ex thinks this is an * unmap command, so it can turn off input mapping. See the usual * ranting in the vi/v_txt_ev.c:txt_unmap() routine. * * PUBLIC: int ex_is_unmap(char *, size_t); */ int ex_is_unmap(char *name, size_t len) { EXCMDLIST const *cp; /* * The command the vi input routines are really interested in * is "unmap!", not just unmap. */ if (len > 0 && name[len - 1] != '!') return (0); --len; return ((cp = ex_comm_search(name, len)) != NULL && cp == &cmds[C_UNMAP]); } /* * ex_comm_search -- * Search for a command name. */ static EXCMDLIST const * ex_comm_search(char *name, size_t len) { EXCMDLIST const *cp; for (cp = cmds; cp->name != NULL; ++cp) { if (cp->name[0] > name[0]) return (NULL); if (cp->name[0] != name[0]) continue; if (strlen(cp->name) >= len && !memcmp(name, cp->name, len)) return (cp); } return (NULL); } /* * ex_badaddr -- * Display a bad address message. * * PUBLIC: void ex_badaddr * PUBLIC:(SCR *, EXCMDLIST const *, enum badaddr, enum nresult); */ void ex_badaddr(SCR *sp, EXCMDLIST const *cp, enum badaddr ba, enum nresult nret) { recno_t lno; switch (nret) { case NUM_OK: break; case NUM_ERR: msgq(sp, M_SYSERR, NULL); return; case NUM_OVER: msgq(sp, M_ERR, "Address value overflow"); return; case NUM_UNDER: msgq(sp, M_ERR, "Address value underflow"); return; } /* * When encountering an address error, tell the user if there's no * underlying file, that's the real problem. */ if (sp->ep == NULL) { ex_emsg(sp, cp != NULL ? cp->name : NULL, EXM_NOFILEYET); return; } switch (ba) { case A_COMBO: msgq(sp, M_ERR, "Illegal address combination"); break; case A_EOF: if (db_last(sp, &lno)) return; if (lno != 0) { msgq(sp, M_ERR, "Illegal address: only %'lu lines in the file", lno); break; } /* FALLTHROUGH */ case A_EMPTY: msgq(sp, M_ERR, "Illegal address: the file is empty"); break; case A_NOTSET: abort(); /* NOTREACHED */ case A_ZERO: msgq(sp, M_ERR, "The %s command doesn't permit an address of 0", cp->name); break; } return; } #if defined(DEBUG) && defined(COMLOG) /* * ex_comlog -- * Log ex commands. */ static void ex_comlog(SCR *sp, EXCMD *ecp) { TRACE(sp, "ecmd: %s", ecp->cmd->name); if (ecp->addrcnt > 0) { TRACE(sp, " a1 %d", ecp->addr1.lno); if (ecp->addrcnt > 1) TRACE(sp, " a2: %d", ecp->addr2.lno); } if (ecp->lineno) TRACE(sp, " line %d", ecp->lineno); if (ecp->flags) TRACE(sp, " flags 0x%x", ecp->flags); if (F_ISSET(&exc, E_BUFFER)) TRACE(sp, " buffer %c", ecp->buffer); if (ecp->argc) for (cnt = 0; cnt < ecp->argc; ++cnt) TRACE(sp, " arg %d: {%s}", cnt, ecp->argv[cnt]->bp); TRACE(sp, "\n"); } #endif /* if defined(DEBUG) && defined(COMLOG) */ ================================================ FILE: ex/ex.h ================================================ /* $OpenBSD: ex.h,v 1.11 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)ex.h 10.24 (Berkeley) 8/12/96 */ #include "../include/compat.h" #define PROMPTCHAR ':' /* Prompt using a colon. */ typedef struct _excmdlist { /* Ex command table structure. */ char *name; /* Command name, underlying function */ int (*fn)(SCR *, EXCMD *); #define E_ADDR1 0x00000001 /* One address. */ #define E_ADDR2 0x00000002 /* Two addresses. */ #define E_ADDR2_ALL 0x00000004 /* Zero/two addresses; zero == all. */ #define E_ADDR2_NONE 0x00000008 /* Zero/two addresses; zero == none. */ #define E_ADDR_ZERO 0x00000010 /* 0 is a legal addr1. */ #define E_ADDR_ZERODEF 0x00000020 /* 0 is default addr1 of empty files */ #define E_AUTOPRINT 0x00000040 /* Command always sets autoprint. */ #define E_CLRFLAG 0x00000080 /* Clear the print (#, l, p) flags. */ #define E_NEWSCREEN 0x00000100 /* Create a new screen. */ #define E_SECURE 0x00000200 /* Permission denied if O_SECURE set */ #define E_VIONLY 0x00000400 /* Meaningful only in vi. */ #define __INUSE1 0xfffff800 /* Same name space as EX_PRIVATE. */ u_int16_t flags; char *syntax; /* Syntax script. */ char *usage; /* Usage line. */ char *help; /* Help line. */ } EXCMDLIST; #define MAXCMDNAMELEN 12 /* Longest command name. */ extern EXCMDLIST const cmds[]; /* Table of ex commands. */ /* * !!! * QUOTING NOTE: * * Historically, .exrc files and EXINIT variables could only use ^V as an * escape character, neither ^Q or a user specified character worked. We * enforce that here, just in case someone depends on it. */ #define IS_ESCAPE(sp, cmdp, ch) \ (F_ISSET((cmdp), E_VLITONLY) ? \ (ch) == CH_LITERAL : KEY_VAL((sp), (ch)) == K_VLNEXT) /* * File state must be checked for each command -- any ex command may be entered * at any time, and most of them won't work well if a file hasn't yet been read * in. Historic vi generally took the easy way out and dropped core. */ #define NEEDFILE(sp, cmdp) { \ if ((sp)->ep == NULL) { \ ex_emsg((sp), (cmdp)->cmd->name, EXM_NOFILEYET); \ return (1); \ } \ } /* Range structures for global and @ commands. */ typedef struct _range RANGE; struct _range { /* Global command range. */ TAILQ_ENTRY(_range) q; /* Linked list of ranges. */ recno_t start, stop; /* Start/stop of the range. */ }; /* Ex command structure. */ struct _excmd { LIST_ENTRY(_excmd) q; /* Linked list of commands */ char *if_name; /* Associated file. */ recno_t if_lno; /* Associated line number. */ /* Clear the structure for the ex parser. */ #define CLEAR_EX_PARSER(cmdp) \ memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags)) char *cp; /* Current command text. */ size_t clen; /* Current command length */ char *save_cmd; /* Remaining command. */ size_t save_cmdlen; /* Remaining command length */ EXCMDLIST const *cmd; /* Command: entry in command table. */ EXCMDLIST rcmd; /* Command: table entry/replacement. */ TAILQ_HEAD(_rh, _range) rq; /* @/global range: linked list. */ recno_t range_lno; /* @/global range: set line number. */ char *o_cp; /* Original @/global command. */ size_t o_clen; /* Original @/global command length. */ #define AGV_AT 0x01 /* @ buffer execution. */ #define AGV_AT_NORANGE 0x02 /* @ buffer execution without range. */ #define AGV_GLOBAL 0x04 /* global command. */ #define AGV_V 0x08 /* v command. */ #define AGV_ALL (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V) u_int8_t agv_flags; /* Clear the structure before each ex command. */ #define CLEAR_EX_CMD(cmdp) { \ u_int32_t L__f = F_ISSET((cmdp), E_PRESERVE); \ memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags - \ (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags)); \ F_SET((cmdp), L__f); \ } CHAR_T buffer; /* Command: named buffer. */ recno_t lineno; /* Command: line number. */ long count; /* Command: signed count. */ long flagoff; /* Command: signed flag offset. */ int addrcnt; /* Command: addresses (0, 1 or 2). */ MARK addr1; /* Command: 1st address. */ MARK addr2; /* Command: 2nd address. */ ARGS **argv; /* Command: array of arguments. */ int argc; /* Command: count of arguments. */ #define E_C_BUFFER 0x00001 /* Buffer name specified. */ #define E_C_CARAT 0x00002 /* ^ flag. */ #define E_C_COUNT 0x00004 /* Count specified. */ #define E_C_COUNT_NEG 0x00008 /* Count was signed negative. */ #define E_C_COUNT_POS 0x00010 /* Count was signed positive. */ #define E_C_DASH 0x00020 /* - flag. */ #define E_C_DOT 0x00040 /* . flag. */ #define E_C_EQUAL 0x00080 /* = flag. */ #define E_C_FORCE 0x00100 /* ! flag. */ #define E_C_HASH 0x00200 /* # flag. */ #define E_C_LIST 0x00400 /* l flag. */ #define E_C_PLUS 0x00800 /* + flag. */ #define E_C_PRINT 0x01000 /* p flag. */ u_int16_t iflags; /* User input information. */ #define __INUSE2 0x000007ff /* Same name space as EXCMDLIST. */ #define E_BLIGNORE 0x00000800 /* Ignore blank lines. */ #define E_NAMEDISCARD 0x00001000 /* Free/discard the name. */ #define E_NOAUTO 0x00002000 /* Don't do autoprint output. */ #define E_NOPRDEF 0x00004000 /* Don't print as default. */ #define E_NRSEP 0x00008000 /* Need to line adjust ex output. */ #define E_OPTNUM 0x00010000 /* Number edit option affected. */ #define E_VLITONLY 0x00020000 /* Use ^V quoting only. */ #define E_PRESERVE 0x0003f800 /* Bits to preserve across commands. */ #define E_ABSMARK 0x00040000 /* Set the absolute mark. */ #define E_ADDR_DEF 0x00080000 /* Default addresses used. */ #define E_DELTA 0x00100000 /* Search address with delta. */ #define E_MODIFY 0x00200000 /* File name expansion modified arg. */ #define E_MOVETOEND 0x00400000 /* Move to the end of the file first */ #define E_NEWLINE 0x00800000 /* Found ending . */ #define E_SEARCH_WMSG 0x01000000 /* Display search-wrapped message. */ #define E_USELASTCMD 0x02000000 /* Use the last command. */ #define E_VISEARCH 0x04000000 /* It's really a vi search command. */ u_int32_t flags; /* Current flags. */ }; /* Ex private, per-screen memory. */ typedef struct _ex_private { TAILQ_HEAD(_tqh, _tagq) tq; /* Tag queue. */ TAILQ_HEAD(_tagfh, _tagf) tagfq;/* Tag file list. */ char *tag_last; /* Saved last tag string. */ CHAR_T *lastbcomm; /* Last bang command. */ ARGS **args; /* Command: argument list. */ int argscnt; /* Command: argument list count. */ int argsoff; /* Command: offset into arguments. */ u_int32_t fdef; /* Saved E_C_* default command flags */ char *ibp; /* File line input buffer. */ size_t ibp_len; /* File line input buffer length. */ /* * Buffers for the ex output. The screen/vi support doesn't do any * character buffering of any kind. We do it here so that we're not * calling the screen output routines on every character. * * XXX * Should change to grow dynamically. */ char obp[1024]; /* Ex output buffer. */ size_t obp_len; /* Ex output buffer length. */ u_int8_t flags; } EX_PRIVATE; #define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private)) /* * Filter actions: * * FILTER_BANG !: filter text through the utility. * FILTER_RBANG !: read from the utility (without stdin). * FILTER_READ read: read from the utility (with stdin). * FILTER_WRITE write: write to the utility, display its output. */ enum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE }; /* Ex common error messages. */ typedef enum { EXM_EMPTYBUF, /* Empty buffer. */ EXM_FILECOUNT, /* Too many file names. */ EXM_NOCANON, /* No terminal interface. */ EXM_NOCANON_F, /* EXM_NOCANO: filter version. */ EXM_NOFILEYET, /* Illegal until a file read in. */ EXM_NOPREVBUF, /* No previous buffer specified. */ EXM_NOPREVRE, /* No previous RE specified. */ EXM_NOSUSPEND, /* No suspension. */ EXM_SECURE, /* Illegal if secure edit option set */ EXM_SECURE_F, /* EXM_SECURE: filter version */ EXM_USAGE /* Standard usage message. */ } exm_t; /* Ex address error types. */ enum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO }; /* Ex common tag error messages. */ typedef enum { TAG_BADLNO, /* Tag line doesn't exist. */ TAG_EMPTY, /* Tags stack is empty. */ TAG_SEARCH /* Tags search pattern wasn't found. */ } tagmsg_t; #include "ex_def.h" #include "ex_extern.h" ================================================ FILE: ex/ex_abbrev.c ================================================ /* $OpenBSD: ex_abbrev.c,v 1.9 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_abbr -- :abbreviate [key replacement] * Create an abbreviation or display abbreviations. * * PUBLIC: int ex_abbr(SCR *, EXCMD *); */ int ex_abbr(SCR *sp, EXCMD *cmdp) { CHAR_T *p; size_t len; switch (cmdp->argc) { case 0: if (seq_dump(sp, SEQ_ABBREV, 0) == 0) msgq(sp, M_INFO, "No abbreviations to display"); return (0); case 2: break; default: abort(); } /* * Check for illegal characters. * * !!! * Another fun one, historically. See vi/v_ntext.c:txt_abbrev() for * details. The bottom line is that all abbreviations have to end * with a "word" character, because it's the transition from word to * non-word characters that triggers the test for an abbreviation. In * addition, because of the way the test is done, there can't be any * transitions from word to non-word character (or vice-versa) other * than between the next-to-last and last characters of the string, * and there can't be any characters. Warn the user. */ if (!inword(cmdp->argv[0]->bp[cmdp->argv[0]->len - 1])) { msgq(sp, M_ERR, "Abbreviations must end with a \"word\" character"); return (1); } for (p = cmdp->argv[0]->bp; *p != '\0'; ++p) if (isblank(p[0])) { msgq(sp, M_ERR, "Abbreviations may not contain tabs or spaces"); return (1); } if (cmdp->argv[0]->len > 2) for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len - 2; len; --len, ++p) if (inword(p[0]) != inword(p[1])) { msgq(sp, M_ERR, "Abbreviations may not mix word/non-word characters, except at the end"); return (1); } if (seq_set(sp, NULL, 0, cmdp->argv[0]->bp, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, SEQ_ABBREV, SEQ_USERDEF)) return (1); F_SET(sp->gp, G_ABBREV); return (0); } /* * ex_unabbr -- :unabbreviate key * Delete an abbreviation. * * PUBLIC: int ex_unabbr(SCR *, EXCMD *); */ int ex_unabbr(SCR *sp, EXCMD *cmdp) { ARGS *ap; ap = cmdp->argv[0]; if (!F_ISSET(sp->gp, G_ABBREV) || seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) { msgq_str(sp, M_ERR, ap->bp, "\"%s\" is not an abbreviation"); return (1); } return (0); } ================================================ FILE: ex/ex_append.c ================================================ /* $OpenBSD: ex_append.c,v 1.14 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" enum which {APPEND, CHANGE, INSERT}; static int ex_aci(SCR *, EXCMD *, enum which); /* * ex_append -- :[line] a[ppend][!] * Append one or more lines of new text after the specified line, * or the current line if no address is specified. * * PUBLIC: int ex_append(SCR *, EXCMD *); */ int ex_append(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, APPEND)); } /* * ex_change -- :[line[,line]] c[hange][!] [count] * Change one or more lines to the input text. * * PUBLIC: int ex_change(SCR *, EXCMD *); */ int ex_change(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, CHANGE)); } /* * ex_insert -- :[line] i[nsert][!] * Insert one or more lines of new text before the specified line, * or the current line if no address is specified. * * PUBLIC: int ex_insert(SCR *, EXCMD *); */ int ex_insert(SCR *sp, EXCMD *cmdp) { return (ex_aci(sp, cmdp, INSERT)); } /* * ex_aci -- * Append, change, insert in ex. */ static int ex_aci(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *p, *t; GS *gp; TEXT *tp; TEXTH tiq; recno_t cnt, lno; size_t len; u_int32_t flags; int need_newline; (void)cnt; gp = sp->gp; NEEDFILE(sp, cmdp); /* * If doing a change, replace lines for as long as possible. Then, * append more lines or delete remaining lines. Changes to an empty * file are appends, inserts are the same as appends to the previous * line. * * !!! * Set the address to which we'll append. We set sp->lno to this * address as well so that autoindent works correctly when get text * from the user. */ lno = cmdp->addr1.lno; sp->lno = lno; if ((cmd == CHANGE || cmd == INSERT) && lno != 0) --lno; /* * !!! * If the file isn't empty, cut changes into the unnamed buffer. */ if (cmd == CHANGE && cmdp->addr1.lno != 0 && (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) || del(sp, &cmdp->addr1, &cmdp->addr2, 1))) return (1); /* * !!! * Anything that was left after the command separator becomes part * of the inserted text. Apparently, it was common usage to enter: * * :g/pattern/append|stuff1 * * and append the line of text "stuff1" to the lines containing the * pattern. It was also historically legal to enter: * * :append|stuff1 * stuff2 * . * * and the text on the ex command line would be appended as well as * the text inserted after it. There was an historic bug however, * that the user had to enter *two* terminating lines (the '.' lines) * to terminate text input mode, in this case. This whole thing * could be taken too far, however. Entering: * * :append|stuff1\ * stuff2 * stuff3 * . * * i.e. mixing and matching the forms confused the historic vi, and, * not only did it take two terminating lines to terminate text input * mode, but the trailing backslashes were retained on the input. We * match historic practice except that we discard the backslashes. * * Input lines specified on the ex command line lines are separated by * s. If there is a trailing delimiter an empty line was * inserted. There may also be a leading delimiter, which is ignored * unless it's also a trailing delimiter. It is possible to encounter * a termination line, i.e. a single '.', in a global command, but not * necessary if the text insert command was the last of the global * commands. */ if (cmdp->save_cmdlen != 0) { for (p = cmdp->save_cmd, len = cmdp->save_cmdlen; len > 0; p = t) { for (t = p; len > 0 && t[0] != '\n'; ++t, --len); if (t != p || len == 0) { if (F_ISSET(sp, SC_EX_GLOBAL) && t - p == 1 && p[0] == '.') { ++t; if (len > 0) --len; break; } if (db_append(sp, 1, lno++, p, t - p)) return (1); } if (len != 0) { ++t; if (--len == 0 && db_append(sp, 1, lno++, "", 0)) return (1); } } /* * If there's any remaining text, we're in a global, and * there's more command to parse. * * !!! * We depend on the fact that non-global commands will eat the * rest of the command line as text input, and before getting * any text input from the user. Otherwise, we'd have to save * off the command text before or during the call to the text * input function below. */ if (len != 0) cmdp->save_cmd = t; cmdp->save_cmdlen = len; } if (F_ISSET(sp, SC_EX_GLOBAL)) { if ((sp->lno = lno) == 0 && db_exist(sp, 1)) sp->lno = 1; return (0); } /* * If not in a global command, read from the terminal. * * If this code is called by vi, we want to reset the terminal and use * ex's line get routine. It actually works fine if we use vi's get * routine, but it doesn't look as nice. Maybe if we had a separate * window or something, but getting a line at a time looks awkward. * However, depending on the screen that we're using, that may not * be possible. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON); return (1); } /* If we're still in the vi screen, move out explicitly. */ need_newline = !F_ISSET(sp, SC_SCR_EXWROTE); F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); if (need_newline) (void)ex_puts(sp, "\n"); /* * !!! * Users of historical versions of vi sometimes get confused * when they enter append mode, and can't seem to get out of * it. Give them an informational message. */ (void)ex_puts(sp, "Entering ex input mode.\n"); (void)ex_fflush(sp); } /* * Set input flags; the ! flag turns off autoindent for append, * change and insert. */ LF_INIT(TXT_DOTTERM | TXT_NUMBER); if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && O_ISSET(sp, O_AUTOINDENT)) LF_SET(TXT_AUTOINDENT); if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); /* * This code can't use the common screen TEXTH structure (sp->tiq), * as it may already be in use, e.g. ":append|s/abc/ABC/" would fail * as we are only halfway through the text when the append code fires. * Use a local structure instead. (The ex code would have to use a * local structure except that we're guaranteed to finish remaining * characters in the common TEXTH structure when they were inserted * into the file, above.) */ memset(&tiq, 0, sizeof(TEXTH)); TAILQ_INIT(&tiq); if (ex_txt(sp, &tiq, 0, flags)) return (1); cnt = 0; TAILQ_FOREACH(tp, &tiq, q) { if (db_append(sp, 1, lno++, tp->lb, tp->len)) return (1); cnt++; } /* * Set sp->lno to the final line number value (correcting for a * possible 0 value) as that's historically correct for the final * line value, whether or not the user entered any text. */ if ((sp->lno = lno) == 0 && db_exist(sp, 1)) sp->lno = 1; return (0); } ================================================ FILE: ex/ex_args.c ================================================ /* $OpenBSD: ex_args.c,v 1.12 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" static int ex_N_next(SCR *, EXCMD *); /* * ex_next -- :next [+cmd] [files] * Edit the next file, optionally setting the list of files. * * !!! * The :next command behaved differently from the :rewind command in * historic vi. See nvi/docs/autowrite for details, but the basic * idea was that it ignored the force flag if the autowrite flag was * set. This implementation handles them all identically. * * PUBLIC: int ex_next(SCR *, EXCMD *); */ int ex_next(SCR *sp, EXCMD *cmdp) { ARGS **argv; FREF *frp; int noargs; char **ap; /* Check for file to move to. */ if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) { msgq(sp, M_ERR, "No more files to edit"); return (1); } if (F_ISSET(cmdp, E_NEWSCREEN)) { /* By default, edit the next file in the old argument list. */ if (cmdp->argc == 0) { if (argv_exp0(sp, cmdp, sp->cargv[1], strlen(sp->cargv[1]))) return (1); return (ex_edit(sp, cmdp)); } return (ex_N_next(sp, cmdp)); } /* Check modification. */ if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); /* Any arguments are a replacement file list. */ if (cmdp->argc) { /* Free the current list. */ if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { for (ap = sp->argv; *ap != NULL; ++ap) free(*ap); free(sp->argv); } F_CLR(sp, SC_ARGNOFREE | SC_ARGRECOVER); sp->cargv = NULL; /* Create a new list. */ CALLOC_RET(sp, sp->argv, cmdp->argc + 1, sizeof(char *)); for (ap = sp->argv, argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) if ((*ap = v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) return (1); *ap = NULL; /* Switch to the first file. */ sp->cargv = sp->argv; if ((frp = file_add(sp, *sp->cargv)) == NULL) return (1); noargs = 0; /* Display a file count with the welcome message. */ F_SET(sp, SC_STATUS_CNT); } else { if ((frp = file_add(sp, sp->cargv[1])) == NULL) return (1); if (F_ISSET(sp, SC_ARGRECOVER)) F_SET(frp, FR_RECOVER); noargs = 1; } if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); if (noargs) ++sp->cargv; F_SET(sp, SC_FSWITCH); return (0); } /* * ex_N_next -- * New screen version of ex_next. */ static int ex_N_next(SCR *sp, EXCMD *cmdp) { SCR *new; FREF *frp; /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if (vs_split(sp, new, 0)) { (void)screen_end(new); return (1); } /* Get a backing file. */ if ((frp = file_add(new, cmdp->argv[0]->bp)) == NULL || file_init(new, frp, NULL, (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* The arguments are a replacement file list. */ new->cargv = new->argv = ex_buildargv(sp, cmdp, NULL); /* Display a file count with the welcome message. */ F_SET(new, SC_STATUS_CNT); /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * ex_prev -- :prev * Edit the previous file. * * PUBLIC: int ex_prev(SCR *, EXCMD *); */ int ex_prev(SCR *sp, EXCMD *cmdp) { FREF *frp; if (sp->cargv == sp->argv) { msgq(sp, M_ERR, "No previous files to edit"); return (1); } if (F_ISSET(cmdp, E_NEWSCREEN)) { if (argv_exp0(sp, cmdp, sp->cargv[-1], strlen(sp->cargv[-1]))) return (1); return (ex_edit(sp, cmdp)); } if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); if ((frp = file_add(sp, sp->cargv[-1])) == NULL) return (1); if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); --sp->cargv; F_SET(sp, SC_FSWITCH); return (0); } /* * ex_rew -- :rew * Re-edit the list of files. * * !!! * Historic practice was that all files would start editing at the beginning * of the file. We don't get this right because we may have multiple screens * and we can't clear the FR_CURSORSET bit for a single screen. I don't see * anyone noticing, but if they do, we'll have to put information into the SCR * structure so we can keep track of it. * * PUBLIC: int ex_rew(SCR *, EXCMD *); */ int ex_rew(SCR *sp, EXCMD *cmdp) { FREF *frp; /* * !!! * Historic practice -- you can rewind to the current file. */ if (sp->argv == NULL) { msgq(sp, M_ERR, "No previous files to rewind"); return (1); } if (file_m1(sp, FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) return (1); /* Switch to the first one. */ sp->cargv = sp->argv; if ((frp = file_add(sp, *sp->cargv)) == NULL) return (1); if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); /* Switch and display a file count with the welcome message. */ F_SET(sp, SC_FSWITCH | SC_STATUS_CNT); return (0); } /* * ex_args -- :args * Display the list of files. * * PUBLIC: int ex_args(SCR *, EXCMD *); */ int ex_args(SCR *sp, EXCMD *cmdp) { int cnt, col, len, sep; char **ap; if (sp->argv == NULL) { (void)msgq(sp, M_ERR, "No file list to display"); return (0); } col = len = sep = 0; for (cnt = 1, ap = sp->argv; *ap != NULL; ++ap) { col += len = strlen(*ap) + sep + (ap == sp->cargv ? 2 : 0); if (col >= sp->cols - 1) { col = len; sep = 0; (void)ex_puts(sp, "\n"); } else if (cnt != 1) { sep = 1; (void)ex_puts(sp, " "); } ++cnt; (void)ex_printf(sp, "%s%s%s", ap == sp->cargv ? "[" : "", *ap, ap == sp->cargv ? "]" : ""); if (INTERRUPTED(sp)) break; } (void)ex_puts(sp, "\n"); return (0); } /* * ex_buildargv -- * Build a new file argument list. * * PUBLIC: char **ex_buildargv(SCR *, EXCMD *, char *); */ char ** ex_buildargv(SCR *sp, EXCMD *cmdp, char *name) { ARGS **argv; int argc; char **ap, **s_argv; argc = cmdp == NULL ? 1 : cmdp->argc; CALLOC(sp, s_argv, argc + 1, sizeof(char *)); if ((ap = s_argv) == NULL) return (NULL); if (cmdp == NULL) { if ((*ap = v_strdup(sp, name, strlen(name))) == NULL) { free(s_argv); return (NULL); } ++ap; } else for (argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) if ((*ap = v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) { while (--ap >= s_argv) free(*ap); free(s_argv); return (NULL); } *ap = NULL; return (s_argv); } ================================================ FILE: ex/ex_argv.c ================================================ /* $OpenBSD: ex_argv.c,v 1.20 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #undef open static int argv_alloc(SCR *, size_t); static int argv_comp(const void *, const void *); static int argv_fexp(SCR *, EXCMD *, char *, size_t, char *, size_t *, char **, size_t *, int); static int argv_lexp(SCR *, EXCMD *, char *); static int argv_sexp(SCR *, char **, size_t *, size_t *); /* * argv_init -- * Build a prototype arguments list. * * PUBLIC: int argv_init(SCR *, EXCMD *); */ int argv_init(SCR *sp, EXCMD *excp) { EX_PRIVATE *exp; exp = EXP(sp); exp->argsoff = 0; argv_alloc(sp, 1); excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp0 -- * Append a string to the argument list. * * PUBLIC: int argv_exp0(SCR *, EXCMD *, char *, size_t); */ int argv_exp0(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) { EX_PRIVATE *exp; exp = EXP(sp); argv_alloc(sp, cmdlen); memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen); exp->args[exp->argsoff]->bp[cmdlen] = '\0'; exp->args[exp->argsoff]->len = cmdlen; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_exp1 -- * Do file name expansion on a string, and append it to the * argument list. * * PUBLIC: int argv_exp1(SCR *, EXCMD *, char *, size_t, int); */ int argv_exp1(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, int is_bang) { size_t blen, len; char *bp, *p, *t; GET_SPACE_RET(sp, bp, blen, 512); len = 0; if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { FREE_SPACE(sp, bp, blen); return (1); } /* If it's empty, we're done. */ if (len != 0) { for (p = bp, t = bp + len; p < t; ++p) if (!isblank(*p)) break; if (p == t) goto ret; } else goto ret; (void)argv_exp0(sp, excp, bp, len); ret: FREE_SPACE(sp, bp, blen); return (0); } /* * argv_exp2 -- * Do file name and shell expansion on a string, and append it to * the argument list. * * PUBLIC: int argv_exp2(SCR *, EXCMD *, char *, size_t); */ int argv_exp2(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) { size_t blen, len, n; int rval; char *bp, *mp, *p; GET_SPACE_RET(sp, bp, blen, 512); #define SHELLECHO "echo " #define SHELLOFFSET (sizeof(SHELLECHO) - 1) memcpy(bp, SHELLECHO, SHELLOFFSET); p = bp + SHELLOFFSET; len = SHELLOFFSET; if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { rval = 1; goto err; } /* * Do shell word expansion -- it's very, very hard to figure out what * magic characters the user's shell expects. Historically, it was a * union of v7 shell and csh meta characters. We match that practice * by default, so ":read \%" tries to read a file named '%'. It would * make more sense to pass any special characters through the shell, * but then, if your shell was csh, the above example will behave * differently in nvi than in vi. If you want to get other characters * passed through to your shell, change the "meta" option. * * To avoid a function call per character, we do a first pass through * the meta characters looking for characters that aren't expected * to be there, and then we can ignore them in the user's argument. */ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) n = 0; else { for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p) if (isblank(*p) || isalnum(*p)) break; p = bp + SHELLOFFSET; n = len - SHELLOFFSET; if (*p != '\0') { for (; n > 0; --n, ++p) if (strchr(mp, *p) != NULL) break; } else for (; n > 0; --n, ++p) if (!isblank(*p) && !isalnum(*p) && strchr(mp, *p) != NULL) break; } /* * If we found a meta character in the string, fork a shell to expand * it. Unfortunately, this is comparatively slow. Historically, it * didn't matter much, since users don't enter meta characters as part * of pathnames that frequently. The addition of filename completion * broke that assumption because it's easy to use. As a result, lots * folks have complained that the expansion code is too slow. So, we * detect filename completion as a special case, and do it internally. * Note that this code assumes that the character is the * match-anything meta character. That feels safe -- if anyone writes * a shell that doesn't follow that convention, I'd suggest giving them * a festive hot-lead enema. */ switch (n) { case 0: p = bp + SHELLOFFSET; len -= SHELLOFFSET; rval = argv_exp3(sp, excp, p, len); break; case 1: if (*p == '*') { *p = '\0'; rval = argv_lexp(sp, excp, bp + SHELLOFFSET); break; } /* FALLTHROUGH */ default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; goto err; } p = bp; rval = argv_exp3(sp, excp, p, len); break; } err: FREE_SPACE(sp, bp, blen); return (rval); } /* * argv_exp3 -- * Take a string and break it up into an argv, which is appended * to the argument list. * * PUBLIC: int argv_exp3(SCR *, EXCMD *, char *, size_t); */ int argv_exp3(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen) { EX_PRIVATE *exp; size_t len; int ch, off; char *ap, *p; for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { /* Skip any leading whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } if (cmdlen == 0) break; /* * Determine the length of this whitespace delimited * argument. * * QUOTING NOTE: * * Skip any character preceded by the user's quoting * character. */ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { ch = *cmd; if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { ++cmd; --cmdlen; } else if (isblank(ch)) break; } /* * Copy the argument into place. * * QUOTING NOTE: * * Lose quote chars. */ argv_alloc(sp, len); off = exp->argsoff; exp->args[off]->len = len; for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) if (IS_ESCAPE(sp, excp, *ap)) ++ap; *p = '\0'; } excp->argv = exp->args; excp->argc = exp->argsoff; return (0); } /* * argv_fexp -- * Do file name and bang command expansion. */ static int argv_fexp(SCR *sp, EXCMD *excp, char *cmd, size_t cmdlen, char *p, size_t *lenp, char **bpp, size_t *blenp, int is_bang) { EX_PRIVATE *exp; char *bp, *t; size_t blen, len, off, tlen; /* Replace file name characters. */ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) switch (*cmd) { case '!': if (!is_bang) goto ins_ch; exp = EXP(sp); if (exp->lastbcomm == NULL) { msgq(sp, M_ERR, "No previous command to replace \"!\""); return (1); } len += tlen = strlen(exp->lastbcomm); off = p - bp; ADD_SPACE_RET(sp, bp, blen, len); p = bp + off; memcpy(p, exp->lastbcomm, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '%': if ((t = sp->frp->name) == NULL) { msgq(sp, M_ERR, "No filename to substitute for %%"); return (1); } tlen = strlen(t); len += tlen; off = p - bp; ADD_SPACE_RET(sp, bp, blen, len); p = bp + off; memcpy(p, t, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '#': if ((t = sp->alt_name) == NULL) { msgq(sp, M_ERR, "No filename to substitute for #"); return (1); } len += tlen = strlen(t); off = p - bp; ADD_SPACE_RET(sp, bp, blen, len); p = bp + off; memcpy(p, t, tlen); p += tlen; F_SET(excp, E_MODIFY); break; case '\\': /* * QUOTING NOTE: * * Strip any backslashes that protected the file * expansion characters. */ if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { ++cmd; --cmdlen; } /* FALLTHROUGH */ default: ins_ch: ++len; off = p - bp; ADD_SPACE_RET(sp, bp, blen, len); p = bp + off; *p++ = *cmd; } /* NULL termination. */ ++len; off = p - bp; ADD_SPACE_RET(sp, bp, blen, len); p = bp + off; *p = '\0'; /* Return the new string length, buffer, buffer length. */ *lenp = len - 1; *bpp = bp; *blenp = blen; return (0); } /* * argv_alloc -- * Make more space for arguments. */ static int argv_alloc(SCR *sp, size_t len) { ARGS *ap; EX_PRIVATE *exp; int cnt, off; /* * Allocate room for another argument, always leaving * enough room for an ARGS structure with a length of 0. */ #define INCREMENT 20 exp = EXP(sp); off = exp->argsoff; if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { cnt = exp->argscnt + INCREMENT; REALLOCARRAY(sp, exp->args, cnt, sizeof(ARGS *)); if (exp->args == NULL) { (void)argv_free(sp); goto mem; } memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); exp->argscnt = cnt; } /* First argument. */ if (exp->args[off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* First argument buffer. */ ap = exp->args[off]; ap->len = 0; if (ap->blen < len + 1) { ap->blen = len + 1; REALLOCARRAY(sp, ap->bp, ap->blen, sizeof(CHAR_T)); if (ap->bp == NULL) { ap->bp = NULL; ap->blen = 0; F_CLR(ap, A_ALLOCATED); mem: msgq(sp, M_SYSERR, NULL); return (1); } F_SET(ap, A_ALLOCATED); } /* Second argument. */ if (exp->args[++off] == NULL) { CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); if (exp->args[off] == NULL) goto mem; } /* 0 length serves as end-of-argument marker. */ exp->args[off]->len = 0; return (0); } /* * argv_free -- * Free up argument structures. * * PUBLIC: int argv_free(SCR *); */ int argv_free(SCR *sp) { EX_PRIVATE *exp; int off; exp = EXP(sp); if (exp->args != NULL) { for (off = 0; off < exp->argscnt; ++off) { if (exp->args[off] == NULL) continue; if (F_ISSET(exp->args[off], A_ALLOCATED)) free(exp->args[off]->bp); free(exp->args[off]); } free(exp->args); } exp->args = NULL; exp->argscnt = 0; exp->argsoff = 0; return (0); } /* * argv_lexp -- * Find all file names matching the prefix and append them to the * buffer. */ static int argv_lexp(SCR *sp, EXCMD *excp, char *path) { struct dirent *dp; DIR *dirp; EX_PRIVATE *exp; int off; size_t dlen, nlen; char *dname, *name, *p; exp = EXP(sp); /* Set up the name and length for comparison. */ if ((p = strrchr(path, '/')) == NULL) { dname = "."; dlen = 0; name = path; } else { if (p == path) { dname = "/"; dlen = 1; } else { *p = '\0'; dname = path; dlen = strlen(path); } name = p + 1; } nlen = strlen(name); if ((dirp = opendir(dname)) == NULL) { msgq_str(sp, M_SYSERR, dname, "%s"); return (1); } for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { if (nlen == 0) { if (dp->d_name[0] == '.') continue; } else { if (D_NAMLEN(dp) < nlen || memcmp(dp->d_name, name, nlen)) continue; } /* Directory + name + slash + NULL. */ argv_alloc(sp, dlen + D_NAMLEN(dp) + 2); p = exp->args[exp->argsoff]->bp; if (dlen != 0) { memcpy(p, dname, dlen); p += dlen; if (dlen > 1 || dname[0] != '/') *p++ = '/'; } memcpy(p, dp->d_name, D_NAMLEN(dp) + 1); exp->args[exp->argsoff]->len = dlen + D_NAMLEN(dp) + 1; ++exp->argsoff; excp->argv = exp->args; excp->argc = exp->argsoff; } closedir(dirp); if (off == exp->argsoff) { /* * If we didn't find a match, complain that the expansion * failed. We can't know for certain that's the error, but * it's a good guess, and it matches historic practice. */ msgq(sp, M_ERR, "Shell expansion failed"); return (1); } qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); return (0); } /* * argv_comp -- * Alphabetic comparison. */ static int argv_comp(const void *a, const void *b) { return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp)); } /* * argv_sexp -- * Fork a shell, pipe a command through it, and read the output into * a buffer. */ static int argv_sexp(SCR *sp, char **bpp, size_t *blenp, size_t *lenp) { enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; FILE *ifp; pid_t pid; size_t blen, len; int ch, std_output[2]; char *bp, *p, *sh, *sh_path; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { msgq(sp, M_ERR, "Shell expansions not supported when the secure edit option is set"); return (1); } sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; /* Local copies of the buffer variables. */ bp = *bpp; blen = *blenp; /* * There are two different processes running through this code, named * the utility (the shell) and the parent. The utility reads standard * input and writes standard output and standard error output. The * parent writes to the utility, reads its standard output and ignores * its standard error output. Historically, the standard error output * was discarded by vi, as it produces a lot of noise when file patterns * don't match. * * The parent reads std_output[0], and the utility writes std_output[1]. */ ifp = NULL; std_output[0] = std_output[1] = -1; if (pipe(std_output) < 0) { msgq(sp, M_SYSERR, "pipe"); return (1); } if ((ifp = fdopen(std_output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* * Do the minimal amount of work possible, the shell is going to run * briefly and then exit. We sincerely hope. */ switch (pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (ifp != NULL) (void)fclose(ifp); else if (std_output[0] != -1) close(std_output[0]); if (std_output[1] != -1) close(std_output[0]); return (1); case 0: /* Utility. */ /* Redirect stdout to the write end of the pipe. */ (void)dup2(std_output[1], STDOUT_FILENO); /* Close the utility's file descriptors. */ (void)close(std_output[0]); (void)close(std_output[1]); (void)close(STDERR_FILENO); /* * XXX * Assume that all shells have -c. */ execl(sh_path, sh, "-c", bp, (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "Error: execl: %s"); _exit(127); default: /* Parent. */ /* Close the pipe ends the parent won't use. */ (void)close(std_output[1]); break; } /* * Copy process standard output into a buffer. * * !!! * Historic vi apparently discarded leading \n and \r's from * the shell output stream. We don't on the grounds that any * shell that does that is broken. */ for (p = bp, len = 0, ch = EOF; (ch = getc(ifp)) != EOF; *p++ = ch, blen -= sizeof(CHAR_T), ++len) if (blen < 5) { ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2); p = bp + len; blen = *blenp - len * sizeof(CHAR_T); } /* Delete the final newline, NULL terminate the string. */ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { --p; --len; } *p = '\0'; *lenp = len; *bpp = bp; /* *blenp is already updated. */ if (ferror(ifp)) goto ioerr; if (fclose(ifp)) { ioerr: msgq_str(sp, M_ERR, sh, "I/O error: %s"); alloc_err: rval = SEXP_ERR; } else rval = SEXP_OK; /* * Wait for the process. If the shell process fails (e.g., "echo $q" * where q wasn't a defined variable) or if the returned string has * no characters or only blank characters, (e.g., "echo $5"), complain * that the shell expansion failed. We can't know for certain that's * the error, but it's a good guess, and it matches historic practice. * This won't catch "echo foo_$5", but that's not a common error and * historic vi didn't catch it either. */ if (proc_wait(sp, pid, sh, 1, 0)) rval = SEXP_EXPANSION_ERR; for (p = bp; len; ++p, --len) if (!isblank(*p)) break; if (len == 0) rval = SEXP_EXPANSION_ERR; if (rval == SEXP_EXPANSION_ERR) msgq(sp, M_ERR, "Shell expansion failed"); return (rval == SEXP_OK ? 0 : 1); } ================================================ FILE: ex/ex_at.c ================================================ /* $OpenBSD: ex_at.c,v 1.14 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_at -- :@[@ | buffer] * :*[* | buffer] * * Execute the contents of the buffer. * * PUBLIC: int ex_at(SCR *, EXCMD *); */ int ex_at(SCR *sp, EXCMD *cmdp) { CB *cbp; CHAR_T name; EXCMD *ecp; RANGE *rp; TEXT *tp; size_t len; char *p; /* * !!! * Historically, [@*] and [@*][@*] executed the most * recently executed buffer in ex mode. */ name = FL_ISSET(cmdp->iflags, E_C_BUFFER) ? cmdp->buffer : '@'; if (name == '@' || name == '*') { if (!F_ISSET(sp, SC_AT_SET)) { ex_emsg(sp, NULL, EXM_NOPREVBUF); return (1); } name = sp->at_lbuf; } sp->at_lbuf = name; F_SET(sp, SC_AT_SET); CBNAME(sp, cbp, name); if (cbp == NULL) { ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); return (1); } /* * !!! * Historically the @ command took a range of lines, and the @ buffer * was executed once per line. The historic vi could be trashed by * this because it didn't notice if the underlying file changed, or, * for that matter, if there were no more lines on which to operate. * For example, take a 10 line file, load "%delete" into a buffer, * and enter :8,10@. * * The solution is a bit tricky. If the user specifies a range, take * the same approach as for global commands, and discard the command * if exit or switch to a new file/screen. If the user doesn't specify * the range, continue to execute after a file/screen switch, which * means @ buffers are still useful in a multi-screen environment. */ CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); TAILQ_INIT(&ecp->rq); CALLOC_RET(sp, rp, 1, sizeof(RANGE)); rp->start = cmdp->addr1.lno; if (F_ISSET(cmdp, E_ADDR_DEF)) { rp->stop = rp->start; FL_SET(ecp->agv_flags, AGV_AT_NORANGE); } else { rp->stop = cmdp->addr2.lno; FL_SET(ecp->agv_flags, AGV_AT); } TAILQ_INSERT_HEAD(&ecp->rq, rp, q); /* * Buffers executed in ex mode or from the colon command line in vi * were ex commands. We can't push it on the terminal queue, since * it has to be executed immediately, and we may be in the middle of * an ex command already. Push the command on the ex command stack. * Build two copies of the command. We need two copies because the * ex parser may step on the command string when it's parsing it. */ len = 0; TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q) { len += tp->len + 1; } MALLOC_RET(sp, ecp->cp, len * 2); ecp->o_cp = ecp->cp; ecp->o_clen = len; ecp->cp[len] = '\0'; /* Copy the buffer into the command space. */ p = ecp->cp + len; TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q) { memcpy(p, tp->lb, tp->len); p += tp->len; *p++ = '\n'; } LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q); return (0); } ================================================ FILE: ex/ex_bang.c ================================================ /* $OpenBSD: ex_bang.c,v 1.13 2025/07/30 22:19:13 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include "errc.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #undef open /* * ex_bang -- :[line [,line]] ! command * * Pass the rest of the line after the ! character to the program named by * the O_SHELL option. * * Historical vi did NOT do shell expansion on the arguments before passing * them, only file name expansion. This means that the O_SHELL program got * "$t" as an argument if that is what the user entered. Also, there's a * special expansion done for the bang command. Any exclamation points in * the user's argument are replaced by the last, expanded ! command. * * There's some fairly amazing slop in this routine to make the different * ways of getting here display the right things. It took a long time to * get it right (wrong?), so be careful. * * PUBLIC: int ex_bang(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; ap = cmdp->argv[0]; if (ap->len == 0) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* Set the "last bang command" remembered value. */ exp = EXP(sp); free(exp->lastbcomm); if ((exp->lastbcomm = strdup(ap->bp)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * If the command was modified by the expansion, it was historically * redisplayed. */ if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { /* * Display the command if modified. Historic ex/vi displayed * the command if it was modified due to file name and/or bang * expansion. If piping lines in vi, it would be immediately * overwritten by any error or line change reporting. */ if (F_ISSET(sp, SC_VI)) vs_update(sp, "!", ap->bp); else { (void)ex_printf(sp, "!%s\n", ap->bp); (void)ex_fflush(sp); } } /* * If no addresses were specified, run the command. If there's an * underlying file, it's been modified and autowrite is set, write * the file back. If the file has been modified, autowrite is not * set and the warn option is set, tell the user about the file. */ if (cmdp->addrcnt == 0) { msg = NULL; if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) { if (O_ISSET(sp, O_AUTOWRITE)) { if (file_aw(sp, FS_ALL)) return (0); } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, SC_EX_SILENT)) msg = "File may be modified since last write."; } /* If we're still in a vi screen, move out explicitly. */ (void)ex_exec_proc(sp, cmdp, ap->bp, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); } /* * If addresses were specified, pipe lines from the file through the * command. * * Historically, vi lines were replaced by both the stdout and stderr * lines of the command, but ex lines by only the stdout lines. This * makes no sense to me, so nvi makes it consistent for both, and * matches vi's historic behavior. */ else { NEEDFILE(sp, cmdp); /* Autoprint is set historically, even if the command fails. */ F_SET(cmdp, E_AUTOPRINT); /* * !!! * Historical vi permitted "!!" in an empty file. When this * happens, we arrive here with two addresses of 1,1 and a * bad attitude. The simple solution is to turn it into a * FILTER_READ operation, with the exception that stdin isn't * opened for the utility, and the cursor position isn't the * same. The only historic glitch (I think) is that we don't * put an empty line into the default cut buffer, as historic * vi did. Imagine, if you can, my disappointment. */ ftype = FILTER_BANG; if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { if (db_last(sp, &lno)) return (1); if (lno == 0) { cmdp->addr1.lno = cmdp->addr2.lno = 0; ftype = FILTER_RBANG; } } rval = ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); (void)rval; /* * If in vi mode, move to the first nonblank. * * !!! * Historic vi wasn't consistent in this area -- if you used * a forward motion it moved to the first nonblank, but if you * did a backward motion it didn't. And, if you followed a * backward motion with a forward motion, it wouldn't move to * the nonblank for either. Going to the nonblank generally * seems more useful and consistent, so we do it. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } else sp->cno = rm.cno; } /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); /* If addresses were specified, apply expandtab to the new text. */ if (cmdp->addrcnt != 0 && O_ISSET(sp, O_EXPANDTAB)) ex_retab(sp, cmdp); /* * XXX * The ! commands never return an error, so that autoprint always * happens in the ex parser. */ return (0); } ================================================ FILE: ex/ex_cd.c ================================================ /* $OpenBSD: ex_cd.c,v 1.15 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_cd -- :cd[!] [directory] * Change directories. * * PUBLIC: int ex_cd(SCR *, EXCMD *); */ int ex_cd(SCR *sp, EXCMD *cmdp) { struct passwd *pw; ARGS *ap; CHAR_T savech; char *dir, *p, *t; char buf[PATH_MAX * 2]; /* * !!! * Historic practice is that the cd isn't attempted if the file has * been modified, unless its name begins with a leading '/' or the * force flag is set. */ if (F_ISSET(sp->ep, F_MODIFIED) && !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') { msgq(sp, M_ERR, "File may be modified since last complete write; write or use ! to override"); return (1); } switch (cmdp->argc) { case 0: /* If no argument, change to the user's home directory. */ if ((dir = getenv("HOME")) == NULL || *dir == '\0') { if ((pw = getpwuid(getuid())) == NULL || pw->pw_dir == NULL || pw->pw_dir[0] == '\0') { msgq(sp, M_ERR, "Unable to find $HOME directory location"); return (1); } dir = pw->pw_dir; } break; case 1: dir = cmdp->argv[0]->bp; break; default: abort(); } /* * Try the current directory first. If this succeeds, don't display * a message, vi didn't historically, and it should be obvious to the * user where they are. */ if (!chdir(dir)) return (0); /* * If moving to the user's home directory, or, the path begins with * "/", "./" or "../", it's the only place we try. */ if (cmdp->argc == 0 || (ap = cmdp->argv[0])->bp[0] == '/' || (ap->len == 1 && ap->bp[0] == '.') || (ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' && (ap->bp[2] == '/' || ap->bp[2] == '\0'))) goto err; /* Try the O_CDPATH option values. */ for (p = t = O_STR(sp, O_CDPATH);; ++p) if (*p == '\0' || *p == ':') { /* * Empty strings specify ".". The only way to get an * empty string is a leading colon, colons in a row, * or a trailing colon. Or, to put it the other way, * if the length is 1 or less, then we're dealing with * ":XXX", "XXX::XXXX" , "XXX:", or "". Since we've * already tried dot, we ignore them all. */ if (t < p - 1) { savech = *p; *p = '\0'; (void)snprintf(buf, sizeof(buf), "%s/%s", t, dir); *p = savech; if (!chdir(buf)) { if (getcwd(buf, sizeof(buf)) != NULL) msgq_str(sp, M_INFO, buf, "New current directory: %s"); return (0); } } t = p + 1; if (*p == '\0') break; } err: msgq_str(sp, M_SYSERR, dir, "%s"); return (1); } ================================================ FILE: ex/ex_cmd.c ================================================ /* $OpenBSD: ex_cmd.c,v 1.12 2018/07/13 20:06:10 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * This array maps ex command names to command functions. * * The order in which command names are listed below is important -- * ambiguous abbreviations are resolved to be the first possible match, * e.g. "r" means "read", not "rewind", because "read" is listed before * "rewind". * * The syntax of the ex commands is unbelievably irregular, and a special * case from beginning to end. Each command has an associated "syntax * script" which describes the "arguments" that are possible. The script * syntax is as follows: * * ! -- ! flag * 1 -- flags: [+-]*[pl#][+-]* * 2 -- flags: [-.+^] * 3 -- flags: [-.+^=] * b -- buffer * c[01+a] -- count (0-N, 1-N, signed 1-N, address offset) * f[N#][or] -- file (a number or N, optional or required) * l -- line * S -- string with file name expansion * s -- string * W -- word string * w[N#][or] -- word (a number or N, optional or required) */ EXCMDLIST const cmds[] = { /* C_SCROLL */ {"\004", ex_pr, E_ADDR2, "", "^D", "scroll lines"}, /* C_BANG */ {"!", ex_bang, E_ADDR2_NONE | E_SECURE, "S", "[line [,line]] ! command", "filter lines through commands or run commands"}, /* C_HASH */ {"#", ex_number, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] # [count] [l]", "display numbered lines"}, /* C_SUBAGAIN */ {"&", ex_subagain, E_ADDR2, "s", "[line [,line]] & [cgr] [count] [#lp]", "repeat the last substitution"}, /* C_STAR */ {"*", ex_at, 0, "b", "* [buffer]", "execute a buffer"}, /* C_SHIFTL */ {"<", ex_shiftl, E_ADDR2|E_AUTOPRINT, "ca1", "[line [,line]] <[<...] [count] [flags]", "shift lines left"}, /* C_EQUAL */ {"=", ex_equal, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "1", "[line] = [flags]", "display line number"}, /* C_SHIFTR */ {">", ex_shiftr, E_ADDR2|E_AUTOPRINT, "ca1", "[line [,line]] >[>...] [count] [flags]", "shift lines right"}, /* C_AT */ {"@", ex_at, E_ADDR2, "b", "@ [buffer]", "execute a buffer"}, /* C_APPEND */ {"append", ex_append, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "!", "[line] a[ppend][!]", "append input to a line"}, /* C_ABBR */ {"abbreviate", ex_abbr, 0, "W", "ab[brev] [word replace]", "specify an input abbreviation"}, /* C_ARGS */ {"args", ex_args, 0, "", "ar[gs]", "display file argument list"}, /* C_BG */ {"bg", ex_bg, E_VIONLY, "", "bg", "put the current screen into the background"}, /* C_CHANGE */ {"change", ex_change, E_ADDR2|E_ADDR_ZERODEF, "!ca", "[line [,line]] c[hange][!] [count]", "change lines to input"}, /* C_CD */ {"cd", ex_cd, 0, "!f1o", "cd[!] [directory]", "change the current directory"}, /* C_CHDIR */ {"chdir", ex_cd, 0, "!f1o", "chd[ir][!] [directory]", "change the current directory"}, /* C_COPY */ {"copy", ex_copy, E_ADDR2|E_AUTOPRINT, "l1", "[line [,line]] co[py] line [flags]", "copy lines elsewhere in the file"}, /* * !!! * Adding new commands starting with 'd' may break the delete command code * in ex_cmd() (the ex parser). Read through the comments there, first. */ /* C_DELETE */ {"delete", ex_delete, E_ADDR2|E_AUTOPRINT, "bca1", "[line [,line]] d[elete][flags] [buffer] [count] [flags]", "delete lines from the file"}, /* C_DISPLAY */ {"display", ex_display, 0, "w1r", "display b[uffers] | s[creens] | t[ags]", "display buffers, screens or tags"}, /* C_EDIT */ {"edit", ex_edit, E_NEWSCREEN, "f1o", "e[dit][!] [+cmd] [file]", "begin editing another file"}, /* C_EX */ {"ex", ex_edit, E_NEWSCREEN, "f1o", "ex[!] [+cmd] [file]", "begin editing another file"}, /* C_EXUSAGE */ {"exusage", ex_usage, 0, "w1o", "[exu]sage [command]", "display ex command usage statement"}, /* C_FILE */ {"file", ex_file, 0, "f1o", "f[ile] [name]", "display (and optionally set) file name"}, /* C_FG */ {"fg", ex_fg, E_NEWSCREEN|E_VIONLY, "f1o", "fg [file]", "bring a backgrounded screen into the foreground"}, /* C_GLOBAL */ {"global", ex_global, E_ADDR2_ALL, "!s", "[line [,line]] g[lobal][!] [;/]RE[;/] [commands]", "execute a global command on lines matching an RE"}, /* C_HELP */ {"help", ex_help, 0, "", "he[lp]", "display help statement"}, /* C_INSERT */ {"insert", ex_insert, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "!", "[line] i[nsert][!]", "insert input before a line"}, /* C_JOIN */ {"join", ex_join, E_ADDR2|E_AUTOPRINT, "!ca1", "[line [,line]] j[oin][!] [count] [flags]", "join lines into a single line"}, /* C_K */ {"k", ex_mark, E_ADDR1, "w1r", "[line] k key", "mark a line position"}, /* C_LIST */ {"list", ex_list, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] l[ist] [count] [#]", "display lines in an unambiguous form"}, /* C_MOVE */ {"move", ex_move, E_ADDR2|E_AUTOPRINT, "l", "[line [,line]] m[ove] line", "move lines elsewhere in the file"}, /* C_MARK */ {"mark", ex_mark, E_ADDR1, "w1r", "[line] ma[rk] key", "mark a line position"}, /* C_MAP */ {"map", ex_map, 0, "!W", "map[!] [keys replace]", "map input or commands to one or more keys"}, /* C_MKEXRC */ {"mkexrc", ex_mkexrc, 0, "!f1r", "mkexrc[!] file", "write a .exrc file"}, /* C_NEXT */ {"next", ex_next, E_NEWSCREEN, "!fN", "n[ext][!] [+cmd] [file ...]", "edit (and optionally specify) the next file"}, /* C_NUMBER */ {"number", ex_number, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] nu[mber] [count] [l]", "change display to number lines"}, /* C_OPEN */ {"open", ex_open, E_ADDR1, "s", "[line] o[pen] [/RE/] [flags]", "enter \"open\" mode (not implemented)"}, /* C_PRINT */ {"print", ex_pr, E_ADDR2|E_CLRFLAG, "ca1", "[line [,line]] p[rint] [count] [#l]", "display lines"}, /* C_PRESERVE */ {"preserve", ex_preserve, 0, "", "pre[serve]", "preserve an edit session for recovery"}, /* C_PREVIOUS */ {"previous", ex_prev, E_NEWSCREEN, "!", "prev[ious][!]", "edit the previous file in the file argument list"}, /* C_PUT */ {"put", ex_put, E_ADDR1|E_AUTOPRINT|E_ADDR_ZERO|E_ADDR_ZERODEF, "b", "[line] pu[t] [buffer]", "append a cut buffer to the line"}, /* C_QUIT */ {"quit", ex_quit, 0, "!", "q[uit][!]", "exit ex/vi or close the current screen"}, /* C_READ */ {"read", ex_read, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, "s", "[line] r[ead] [!cmd | [file]]", "append input from a command or file to the line"}, /* C_RECOVER */ {"recover", ex_recover, 0, "!f1r", "recover[!] file", "recover a saved file"}, /* C_RESIZE */ {"resize", ex_resize, E_VIONLY, "c+", "resize [+-]rows", "grow or shrink the current screen"}, /* C_REWIND */ {"rewind", ex_rew, 0, "!", "rew[ind][!]", "re-edit all the files in the file argument list"}, /* * !!! * Adding new commands starting with 's' may break the substitute command code * in ex_cmd() (the ex parser). Read through the comments there, first. */ /* C_SUBSTITUTE */ {"s", ex_s, E_ADDR2, "s", "[line [,line]] s [[/;]RE[/;]repl[/;] [cgr] [count] [#lp]]", "substitute on lines matching an RE"}, /* C_SCRIPT */ {"script", ex_script, E_SECURE, "!f1o", "sc[ript][!] [file]", "run a shell in a screen"}, /* C_SET */ {"set", ex_set, 0, "wN", "se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]", "set options (use \":set all\" to see all options)"}, /* C_SHELL */ {"shell", ex_shell, E_SECURE, "", "sh[ell]", "suspend editing and run a shell"}, /* C_SOURCE */ {"source", ex_source, 0, "f1r", "so[urce] file", "read a file of ex commands"}, /* C_STOP */ {"stop", ex_stop, E_SECURE, "!", "st[op][!]", "suspend the edit session"}, /* C_SUSPEND */ {"suspend", ex_stop, E_SECURE, "!", "su[spend][!]", "suspend the edit session"}, /* C_T */ {"t", ex_copy, E_ADDR2|E_AUTOPRINT, "l1", "[line [,line]] t line [flags]", "copy lines elsewhere in the file"}, /* C_TAG */ {"tag", ex_tag_push, E_NEWSCREEN, "!w1o", "ta[g][!] [string]", "edit the file containing the tag"}, /* C_TAGNEXT */ {"tagnext", ex_tag_next, 0, "!", "tagn[ext][!]", "move to the next tag"}, /* C_TAGPOP */ {"tagpop", ex_tag_pop, 0, "!w1o", "tagp[op][!] [number | file]", "return to the previous group of tags"}, /* C_TAGPREV */ {"tagprev", ex_tag_prev, 0, "!", "tagpr[ev][!]", "move to the previous tag"}, /* C_TAGTOP */ {"tagtop", ex_tag_top, 0, "!", "tagt[op][!]", "discard all tags"}, /* C_UNDO */ {"undo", ex_undo, E_AUTOPRINT, "", "u[ndo]", "undo the most recent change"}, /* C_UNABBREVIATE */ {"unabbreviate",ex_unabbr, 0, "w1r", "una[bbrev] word", "delete an abbreviation"}, /* C_UNMAP */ {"unmap", ex_unmap, 0, "!w1r", "unm[ap][!] word", "delete an input or command map"}, /* C_V */ {"v", ex_v, E_ADDR2_ALL, "s", "[line [,line]] v [;/]RE[;/] [commands]", "execute a global command on lines NOT matching an RE"}, /* C_VERSION */ {"version", ex_version, 0, "", "version", "display the program version information"}, /* C_VISUAL_EX */ {"visual", ex_visual, E_ADDR1|E_ADDR_ZERODEF, "2c11", "[line] vi[sual] [-|.|+|^] [window_size] [flags]", "enter visual (vi) mode from ex mode"}, /* C_VISUAL_VI */ {"visual", ex_edit, E_NEWSCREEN, "f1o", "vi[sual][!] [+cmd] [file]", "edit another file (from vi mode only)"}, /* C_VIUSAGE */ {"viusage", ex_viusage, 0, "w1o", "[viu]sage [key]", "display vi key usage statement"}, /* C_WRITE */ {"write", ex_write, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] w[rite][!] [ !cmd | [>>] [file]]", "write the file"}, /* C_WN */ {"wn", ex_wn, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] wn[!] [>>] [file]", "write the file and switch to the next file"}, /* C_WQ */ {"wq", ex_wq, E_ADDR2_ALL|E_ADDR_ZERODEF, "!s", "[line [,line]] wq[!] [>>] [file]", "write the file and exit"}, /* C_XIT */ {"xit", ex_xit, E_ADDR2_ALL|E_ADDR_ZERODEF, "!f1o", "[line [,line]] x[it][!] [file]", "write if modified and exit"}, /* C_YANK */ {"yank", ex_yank, E_ADDR2, "bca", "[line [,line]] ya[nk] [buffer] [count]", "copy lines to a cut buffer"}, /* C_Z */ {"z", ex_z, E_ADDR1, "3c01", "[line] z [-|.|+|^|=] [count] [flags]", "display different screens of the file"}, /* C_SUBTILDE */ {"~", ex_subtilde, E_ADDR2, "s", "[line [,line]] ~ [cgr] [count] [#lp]", "replace previous RE with previous replacement string"}, {NULL, NULL, 0, NULL, NULL, NULL}, }; ================================================ FILE: ex/ex_delete.c ================================================ /* $OpenBSD: ex_delete.c,v 1.7 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags] * * Delete lines from the file. * * PUBLIC: int ex_delete(SCR *, EXCMD *); */ int ex_delete(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); /* * !!! * Historically, lines deleted in ex were not placed in the numeric * buffers. We follow historic practice so that we don't overwrite * vi buffers accidentally. */ if (cut(sp, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* Delete the lines. */ if (del(sp, &cmdp->addr1, &cmdp->addr2, 1)) return (1); /* Set the cursor to the line after the last line deleted. */ sp->lno = cmdp->addr1.lno; /* Or the last line in the file if deleted to the end of the file. */ if (db_last(sp, &lno)) return (1); if (sp->lno > lno) sp->lno = lno; return (0); } ================================================ FILE: ex/ex_display.c ================================================ /* $OpenBSD: ex_display.c,v 1.13 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "tag.h" static int bdisplay(SCR *); static void db(SCR *, CB *, CHAR_T *); /* * ex_display -- :display b[uffers] | s[creens] | t[ags] * * Display buffers, tags or screens. * * PUBLIC: int ex_display(SCR *, EXCMD *); */ int ex_display(SCR *sp, EXCMD *cmdp) { switch (cmdp->argv[0]->bp[0]) { case 'b': #undef ARG #define ARG "buffers" if (cmdp->argv[0]->len >= sizeof(ARG) || memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) break; return (bdisplay(sp)); case 's': #undef ARG #define ARG "screens" if (cmdp->argv[0]->len >= sizeof(ARG) || memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) break; return (ex_sdisplay(sp)); case 't': #undef ARG #define ARG "tags" if (cmdp->argv[0]->len >= sizeof(ARG) || memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) break; return (ex_tag_display(sp)); } ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } /* * bdisplay -- * * Display buffers. */ static int bdisplay(SCR *sp) { CB *cbp; if (LIST_FIRST(&sp->gp->cutq) == NULL && sp->gp->dcbp == NULL) { msgq(sp, M_INFO, "No cut buffers to display"); return (0); } /* Display regular cut buffers. */ LIST_FOREACH(cbp, &sp->gp->cutq, q) { if (isdigit(cbp->name)) continue; if (!TAILQ_EMPTY(&cbp->textq)) db(sp, cbp, NULL); if (INTERRUPTED(sp)) return (0); } /* Display numbered buffers. */ LIST_FOREACH(cbp, &sp->gp->cutq, q) { if (!isdigit(cbp->name)) continue; if (!TAILQ_EMPTY(&cbp->textq)) db(sp, cbp, NULL); if (INTERRUPTED(sp)) return (0); } /* Display default buffer. */ if ((cbp = sp->gp->dcbp) != NULL) db(sp, cbp, "default buffer"); return (0); } /* * db -- * Display a buffer. */ static void db(SCR *sp, CB *cbp, CHAR_T *name) { CHAR_T *p; TEXT *tp; size_t len; (void)ex_printf(sp, "********** %s%s\n", name == NULL ? KEY_NAME(sp, cbp->name) : name, F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)"); TAILQ_FOREACH(tp, &cbp->textq, q) { for (len = tp->len, p = tp->lb; len--; ++p) { (void)ex_puts(sp, KEY_NAME(sp, *p)); if (INTERRUPTED(sp)) return; } (void)ex_puts(sp, "\n"); } } ================================================ FILE: ex/ex_edit.c ================================================ /* $OpenBSD: ex_edit.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" static int ex_N_edit(SCR *, EXCMD *, FREF *, int); /* * ex_edit -- :e[dit][!] [+cmd] [file] * :ex[!] [+cmd] [file] * :vi[sual][!] [+cmd] [file] * * Edit a file; if none specified, re-edit the current file. The third * form of the command can only be executed while in vi mode. See the * hack in ex.c:ex_cmd(). * * !!! * Historic vi didn't permit the '+' command form without specifying * a file name as well. This seems unreasonable, so we support it * regardless. * * PUBLIC: int ex_edit(SCR *, EXCMD *); */ int ex_edit(SCR *sp, EXCMD *cmdp) { FREF *frp; int attach, setalt; switch (cmdp->argc) { case 0: /* * If the name has been changed, we edit that file, not the * original name. If the user was editing a temporary file * (or wasn't editing any file), create another one. The * reason for not reusing temporary files is that there is * special exit processing of them, and reuse is tricky. */ frp = sp->frp; if (sp->ep == NULL || F_ISSET(frp, FR_TMPFILE)) { if ((frp = file_add(sp, NULL)) == NULL) return (1); attach = 0; } else attach = 1; setalt = 0; break; case 1: if ((frp = file_add(sp, cmdp->argv[0]->bp)) == NULL) return (1); attach = 0; setalt = 1; set_alt_name(sp, cmdp->argv[0]->bp); break; default: abort(); } if (F_ISSET(cmdp, E_NEWSCREEN)) return (ex_N_edit(sp, cmdp, frp, attach)); /* * Check for modifications. * * !!! * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit. */ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); /* Switch files. */ if (file_init(sp, frp, NULL, (setalt ? FS_SETALT : 0) | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); F_SET(sp, SC_FSWITCH); return (0); } /* * ex_N_edit -- * New screen version of ex_edit. */ static int ex_N_edit(SCR *sp, EXCMD *cmdp, FREF *frp, int attach) { SCR *new; /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if (vs_split(sp, new, 0)) { (void)screen_end(new); return (1); } /* Get a backing file. */ if (attach) { /* Copy file state, keep the screen and cursor the same. */ new->ep = sp->ep; ++new->ep->refcnt; new->frp = frp; new->frp->flags = sp->frp->flags; new->lno = sp->lno; new->cno = sp->cno; } else if (file_init(new, frp, NULL, (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* Create the argument list. */ new->cargv = new->argv = ex_buildargv(sp, NULL, frp->name); /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } ================================================ FILE: ex/ex_equal.c ================================================ /* $OpenBSD: ex_equal.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_equal -- :address = * * PUBLIC: int ex_equal(SCR *, EXCMD *); */ int ex_equal(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); /* * Print out the line number matching the specified address, * or the number of the last line in the file if no address * specified. * * !!! * Historically, ":0=" displayed 0, and ":=" or ":1=" in an * empty file displayed 1. Until somebody complains loudly, * we're going to do it right. The tables in excmd.c permit * lno to get away with any address from 0 to the end of the * file, which, in an empty file, is 0. */ if (F_ISSET(cmdp, E_ADDR_DEF)) { if (db_last(sp, &lno)) return (1); } else lno = cmdp->addr1.lno; (void)ex_printf(sp, "%ld\n", lno); return (0); } ================================================ FILE: ex/ex_file.c ================================================ /* $OpenBSD: ex_file.c,v 1.9 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_file -- :f[ile] [name] * Change the file's name and display the status line. * * PUBLIC: int ex_file(SCR *, EXCMD *); */ int ex_file(SCR *sp, EXCMD *cmdp) { CHAR_T *p; FREF *frp; NEEDFILE(sp, cmdp); switch (cmdp->argc) { case 0: break; case 1: frp = sp->frp; /* Make sure can allocate enough space. */ if ((p = v_strdup(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len)) == NULL) return (1); /* If already have a file name, it becomes the alternate. */ if (!F_ISSET(frp, FR_TMPFILE)) set_alt_name(sp, frp->name); /* Free the previous name. */ free(frp->name); frp->name = p; /* * The file has a real name, it's no longer a temporary, * clear the temporary file flags. */ F_CLR(frp, FR_TMPEXIT | FR_TMPFILE); /* Have to force a write if the file exists, next time. */ F_SET(frp, FR_NAMECHANGE); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); break; default: abort(); } msgq_status(sp, sp->lno, MSTAT_SHOWLAST); return (0); } ================================================ FILE: ex/ex_filter.c ================================================ /* $OpenBSD: ex_filter.c,v 1.15 2016/08/01 18:27:35 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #undef open static int filter_ldisplay(SCR *, FILE *); /* * ex_filter -- * Run a range of lines through a filter utility and optionally * replace the original text with the stdout/stderr output of * the utility. * * PUBLIC: int ex_filter(SCR *, * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype); */ int ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, char *cmd, enum filtertype ftype) { FILE *ifp, *ofp; pid_t parent_writer_pid, utility_pid; recno_t nread; int input[2], output[2], fd, rval; char *name, tname[] = "/tmp/vi.XXXXXX"; rval = 0; /* Set return cursor position, which is never less than line 1. */ *rp = *fm; if (rp->lno == 0) rp->lno = 1; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * There are three different processes running through this code. * They are the utility, the parent-writer and the parent-reader. * The parent-writer is the process that writes from the file to * the utility, the parent reader is the process that reads from * the utility. * * Input and output are named from the utility's point of view. * The utility reads from input[0] and the parent(s) write to * input[1]. The parent(s) read from output[0] and the utility * writes to output[1]. * * !!! * Historically, in the FILTER_READ case, the utility reads from * the terminal (e.g. :r! cat works). Otherwise open up utility * input pipe. */ ofp = NULL; input[0] = input[1] = output[0] = output[1] = -1; if (ftype == FILTER_BANG) { fd = mkstemp(tname); if (fd == -1) { msgq(sp, M_SYSERR, "Unable to create temporary file"); if (fd != -1) { (void)close(fd); (void)unlink(tname); } goto err; } if (unlink(tname) == -1) msgq(sp, M_SYSERR, "unlink"); if ((ifp = fdopen(fd, "w")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); (void)close(fd); goto err; } if ((input[0] = dup(fd)) == -1) { msgq(sp, M_SYSERR, "dup"); (void)fclose(ifp); goto err; } /* * Write the selected lines into the temporary file. * This instance of ifp is closed by ex_writefp. */ if (ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1)) goto err; if (lseek(input[0], 0, SEEK_SET) == -1) { msgq(sp, M_SYSERR, "lseek"); goto err; } } else if (ftype != FILTER_READ && pipe(input) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } /* Open up utility output pipe. */ if (pipe(output) < 0) { msgq(sp, M_SYSERR, "pipe"); goto err; } if ((ofp = fdopen(output[0], "r")) == NULL) { msgq(sp, M_SYSERR, "fdopen"); goto err; } /* Fork off the utility process. */ switch (utility_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); if (ofp != NULL) (void)fclose(ofp); else if (output[0] != -1) (void)close(output[0]); if (output[1] != -1) (void)close(output[1]); return (1); case 0: /* Utility. */ /* * Redirect stdin from the read end of the input pipe, and * redirect stdout/stderr to the write end of the output pipe. * * !!! * Historically, ex only directed stdout into the input pipe, * letting stderr come out on the terminal as usual. Vi did * not, directing both stdout and stderr into the input pipe. * We match that practice in both ex and vi for consistency. */ if (input[0] != -1) (void)dup2(input[0], STDIN_FILENO); (void)dup2(output[1], STDOUT_FILENO); (void)dup2(output[1], STDERR_FILENO); /* Close the utility's file descriptors. */ if (input[0] != -1) (void)close(input[0]); if (input[1] != -1) (void)close(input[1]); (void)close(output[0]); (void)close(output[1]); if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit (127); /* NOTREACHED */ default: /* Parent-reader, parent-writer. */ /* Close the pipe ends neither parent will use. */ if (input[0] != -1) (void)close(input[0]); (void)close(output[1]); break; } /* * FILTER_RBANG, FILTER_READ: * * Reading is the simple case -- we don't need a parent writer, * so the parent reads the output from the read end of the output * pipe until it finishes, then waits for the child. Ex_readfp * appends to the MARK, and closes ofp. * * For FILTER_RBANG, there is nothing to write to the utility. * Make sure it doesn't wait forever by closing its standard * input. * * !!! * Set the return cursor to the last line read in for FILTER_READ. * Historically, this behaves differently from ":r file" command, * which leaves the cursor at the first line read in. Check to * make sure that it's not past EOF because we were reading into an * empty file. */ if (ftype == FILTER_RBANG || ftype == FILTER_READ) { if (ftype == FILTER_RBANG) (void)close(input[1]); if (ex_readfp(sp, "filter", ofp, fm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; if (ftype == FILTER_READ) { if (fm->lno == 0) rp->lno = nread; else rp->lno += nread; } } /* * FILTER_WRITE * * Here we need both a reader and a writer. Temporary files are * expensive and we'd like to avoid disk I/O. Using pipes has the * obvious starvation conditions. It's done as follows: * * fork * child * write lines out * exit * parent * read and display lines * wait for child * * We get away without locking the underlying database because we know * that filter_ldisplay() does not modify it. When the DB code has * locking, we should treat vi as if it were multiple applications * sharing a database, and do the required locking. If necessary a * work-around would be to do explicit locking in the line.c:db_get() * code, based on the flag set here. */ if (ftype == FILTER_WRITE) { F_SET(sp->ep, F_MULTILOCK); switch (parent_writer_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); (void)close(input[1]); (void)close(output[0]); rval = 1; break; case 0: /* Parent-writer. */ /* * Write the selected lines to the write end of the * input pipe. This instance of ifp is closed by * ex_writefp. */ (void)close(output[0]); if ((ifp = fdopen(input[1], "w")) == NULL) _exit (1); _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1)); /* NOTREACHED */ default: /* Parent-reader. */ (void)close(input[1]); /* * Read the output from the read end of the output * pipe and display it. Filter_ldisplay closes ofp. */ if (filter_ldisplay(sp, ofp)) rval = 1; /* Wait for the parent-writer. */ if (proc_wait(sp, parent_writer_pid, "parent-writer", 0, 1)) rval = 1; break; } F_CLR(sp->ep, F_MULTILOCK); } /* * FILTER_BANG * * Here we need a temporary file because our database lacks locking. * * XXX * Temporary files are expensive and we'd like to avoid disk I/O. * When the DB code has locking, we should treat vi as if it were * multiple applications sharing a database, and do the required * locking. If necessary a work-around would be to do explicit * locking in the line.c:db_get() code, based on F_MULTILOCK flag set * here. */ if (ftype == FILTER_BANG) { /* * Read the output from the read end of the output * pipe. Ex_readfp appends to the MARK and closes * ofp. */ if (ex_readfp(sp, "filter", ofp, tm, &nread, 1)) rval = 1; sp->rptlines[L_ADDED] += nread; /* Delete any lines written to the utility. */ if (rval == 0 && (cut(sp, NULL, fm, tm, CUT_LINEMODE) || del(sp, fm, tm, 1))) { rval = 1; goto uwait; } /* * If the filter had no output, we may have just deleted * the cursor. Don't do any real error correction, we'll * try and recover later. */ if (rp->lno > 1 && !db_exist(sp, rp->lno)) --rp->lno; } /* * !!! * Ignore errors on vi file reads, to make reads prettier. It's * completely inconsistent, and historic practice. */ uwait: return (proc_wait(sp, utility_pid, cmd, ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval); } /* * filter_ldisplay -- * Display output from a utility. * * !!! * Historically, the characters were passed unmodified to the terminal. * We use the ex print routines to make sure they're printable. */ static int filter_ldisplay(SCR *sp, FILE *fp) { size_t len; EX_PRIVATE *exp; for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) if (ex_ldisplay(sp, exp->ibp, len, 0, 0)) break; if (ferror(fp)) msgq(sp, M_SYSERR, "filter read"); (void)fclose(fp); return (0); } ================================================ FILE: ex/ex_global.c ================================================ /* $OpenBSD: ex_global.c,v 1.17 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {GLOBAL, V}; static int ex_g_setup(SCR *, EXCMD *, enum which); /* * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] * Exec on lines matching a pattern. * * PUBLIC: int ex_global(SCR *, EXCMD *); */ int ex_global(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL)); } /* * ex_v -- [line [,line]] v /pattern/ [commands] * Exec on lines not matching a pattern. * * PUBLIC: int ex_v(SCR *, EXCMD *); */ int ex_v(SCR *sp, EXCMD *cmdp) { return (ex_g_setup(sp, cmdp, V)); } /* * ex_g_setup -- * Ex global and v commands. */ static int ex_g_setup(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *ptrn, *p, *t; EXCMD *ecp; MARK abs_mark; RANGE *rp; busy_t btype; recno_t start, end; regex_t *re; regmatch_t match[1]; size_t len; int cnt, delim, eval; char *dbp; NEEDFILE(sp, cmdp); if (F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "The %s command can't be used as part of a global or v command", cmdp->cmd->name); return (1); } /* * Skip leading white space. Historic vi allowed any non-alphanumeric * to serve as the global command delimiter. */ if (cmdp->argc == 0) goto usage; for (p = cmdp->argv[0]->bp; isblank(*p); ++p); if (*p == '\0' || isalnum(*p) || *p == '\\' || *p == '|' || *p == '\n') { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } delim = *p++; /* * Get the pattern string, toss escaped characters. * * QUOTING NOTE: * Only toss an escaped character if it escapes a delimiter. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * NULL terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; } *t++ = *p++; } /* If the pattern string is empty, use the last one. */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); } else { /* Compile the RE. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); /* * Set saved RE. Historic practice is that globals set * direction as well as the RE. */ sp->searchdir = FORWARD; } re = &sp->re_c; (void)re; /* The global commands always set the previous context mark. */ abs_mark.lno = sp->lno; abs_mark.cno = sp->cno; if (mark_set(sp, ABSMARK1, &abs_mark, 1)) return (1); /* Get an EXCMD structure. */ CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); TAILQ_INIT(&ecp->rq); /* * Get a copy of the command string; the default command is print. * Don't worry about a set of s with no command, that will * default to print in the ex parser. We need to have two copies * because the ex parser may step on the command string when it's * parsing it. */ if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) { p = "pp"; len = 1; } MALLOC_RET(sp, ecp->cp, len * 2); ecp->o_cp = ecp->cp; ecp->o_clen = len; memcpy(ecp->cp + len, p, len); ecp->range_lno = OOBLNO; FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V); LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q); /* * For each line... The semantics of global matching are that we first * have to decide which lines are going to get passed to the command, * and then pass them to the command, ignoring other changes. There's * really no way to do this in a single pass, since arbitrary line * creation, deletion and movement can be done in the ex command. For * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d". * What we do is create linked list of lines that are tracked through * each ex command. There's a callback routine which the DB interface * routines call when a line is created or deleted. This doesn't help * the layering much. */ btype = BUSY_ON; cnt = INTERRUPT_CHECK; for (start = cmdp->addr1.lno, end = cmdp->addr2.lno; start <= end; ++start) { if (cnt-- == 0) { if (INTERRUPTED(sp)) { LIST_REMOVE(ecp, q); free(ecp->cp); free(ecp); break; } search_busy(sp, btype); btype = BUSY_UPDATE; cnt = INTERRUPT_CHECK; } if (db_get(sp, start, DBG_FATAL, &dbp, &len)) return (1); match[0].rm_so = 0; match[0].rm_eo = len; switch (eval = regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) { case 0: if (cmd == V) continue; break; case REG_NOMATCH: if (cmd == GLOBAL) continue; break; default: re_error(sp, eval, &sp->re_c); break; } /* If follows the last entry, extend the last entry's range. */ if ((rp = TAILQ_LAST(&ecp->rq, _rh)) && rp->stop == start - 1) { ++rp->stop; continue; } /* Allocate a new range, and append it to the list. */ CALLOC(sp, rp, 1, sizeof(RANGE)); if (rp == NULL) return (1); rp->start = rp->stop = start; TAILQ_INSERT_TAIL(&ecp->rq, rp, q); } search_busy(sp, BUSY_OFF); return (0); } /* * ex_g_insdel -- * Update the ranges based on an insertion or deletion. * * PUBLIC: int ex_g_insdel(SCR *, lnop_t, recno_t); */ int ex_g_insdel(SCR *sp, lnop_t op, recno_t lno) { EXCMD *ecp; RANGE *nrp, *rp; /* All insert/append operations are done as inserts. */ if (op == LINE_APPEND) abort(); if (op == LINE_RESET) return (0); LIST_FOREACH(ecp, &sp->gp->ecq, q) { if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V)) continue; for (rp = TAILQ_FIRST(&ecp->rq); rp != NULL; rp = nrp) { nrp = TAILQ_NEXT(rp, q); /* If range less than the line, ignore it. */ if (rp->stop < lno) continue; /* * If range greater than the line, decrement or * increment the range. */ if (rp->start > lno) { if (op == LINE_DELETE) { --rp->start; --rp->stop; } else { ++rp->start; ++rp->stop; } continue; } /* * Lno is inside the range, decrement the end point * for deletion, and split the range for insertion. * In the latter case, since we're inserting a new * element, neither range can be exhausted. */ if (op == LINE_DELETE) { if (rp->start > --rp->stop) { TAILQ_REMOVE(&ecp->rq, rp, q); free(rp); } } else { CALLOC_RET(sp, nrp, 1, sizeof(RANGE)); nrp->start = lno + 1; nrp->stop = rp->stop + 1; rp->stop = lno - 1; TAILQ_INSERT_AFTER(&ecp->rq, rp, nrp, q); rp = nrp; (void)rp; } } /* * If the command deleted/inserted lines, the cursor moves to * the line after the deleted/inserted line. */ ecp->range_lno = lno; } return (0); } ================================================ FILE: ex/ex_init.c ================================================ /* $OpenBSD: ex_init.c,v 1.19 2021/10/24 21:24:17 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "tag.h" #include "pathnames.h" #undef open enum rc { NOEXIST, NOPERM, RCOK }; static enum rc exrc_isok(SCR *, struct stat *, int *, char *, int, int); static int ex_run_file(SCR *, int, char *); /* * ex_screen_copy -- * Copy ex screen. * * PUBLIC: int ex_screen_copy(SCR *, SCR *); */ int ex_screen_copy(SCR *orig, SCR *sp) { EX_PRIVATE *oexp, *nexp; /* Create the private ex structure. */ CALLOC_RET(orig, nexp, 1, sizeof(EX_PRIVATE)); sp->ex_private = nexp; /* Initialize queues. */ TAILQ_INIT(&nexp->tq); TAILQ_INIT(&nexp->tagfq); if (orig == NULL) { } else { oexp = EXP(orig); if (oexp->lastbcomm != NULL && (nexp->lastbcomm = strdup(oexp->lastbcomm)) == NULL) { msgq(sp, M_SYSERR, NULL); return(1); } if (ex_tag_copy(orig, sp)) return (1); } return (0); } /* * ex_screen_end -- * End a vi screen. * * PUBLIC: int ex_screen_end(SCR *); */ int ex_screen_end(SCR *sp) { EX_PRIVATE *exp; int rval; if ((exp = EXP(sp)) == NULL) return (0); rval = 0; /* Close down script connections. */ if (F_ISSET(sp, SC_SCRIPT) && sscr_end(sp)) rval = 1; if (argv_free(sp)) rval = 1; free(exp->ibp); free(exp->lastbcomm); if (ex_tag_free(sp)) rval = 1; /* Free private memory. */ free(exp); sp->ex_private = NULL; return (rval); } /* * ex_optchange -- * Handle change of options for ex. * * PUBLIC: int ex_optchange(SCR *, int, char *, unsigned long *); */ int ex_optchange(SCR *sp, int offset, char *str, unsigned long *valp) { switch (offset) { case O_TAGS: return (ex_tagf_alloc(sp, str)); } return (0); } /* * ex_exrc -- * Read the EXINIT environment variable and the startup exrc files, * and execute their commands. * * PUBLIC: int ex_exrc(SCR *); */ int ex_exrc(SCR *sp) { struct stat hsb, lsb; char *p, path[PATH_MAX]; int fd; /* * Source the system, environment, $HOME and local .exrc values. * Vi historically didn't check $HOME/.exrc if the environment * variable EXINIT was set. This is all done before the file is * read in, because things in the .exrc information can set, for * example, the recovery directory. * * !!! * While nvi can handle any of the options settings of historic vi, * the converse is not true. Since users are going to have to have * files and environmental variables that work with both, we use nvi * versions of both the $HOME and local startup files if they exist, * otherwise the historic ones. * * !!! * For a discussion of permissions and when what .exrc files are * read, see the comment above the exrc_isok() function below. * * !!! * If the user started the historic of vi in $HOME, vi read the user's * .exrc file twice, as $HOME/.exrc and as ./.exrc. We avoid this, as * it's going to make some commands behave oddly, and I can't imagine * anyone depending on it. */ switch (exrc_isok(sp, &hsb, &fd, _PATH_SYSEXRC, 1, 0)) { case NOEXIST: case NOPERM: break; case RCOK: if (ex_run_file(sp, fd, _PATH_SYSEXRC)) return (1); break; } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); if ((p = getenv("NEXINIT")) != NULL) { if (ex_run_str(sp, "NEXINIT", p, strlen(p), 1, 0)) return (1); } else if ((p = getenv("EXINIT")) != NULL) { if (ex_run_str(sp, "EXINIT", p, strlen(p), 1, 0)) return (1); } else if ((p = getenv("HOME")) != NULL && *p) { (void)snprintf(path, sizeof(path), "%s/%s", p, _PATH_NEXRC); switch (exrc_isok(sp, &hsb, &fd, path, 0, 1)) { case NOEXIST: (void)snprintf(path, sizeof(path), "%s/%s", p, _PATH_EXRC); if (exrc_isok(sp, &hsb, &fd, path, 0, 1) == RCOK && ex_run_file(sp, fd, path)) return (1); break; case NOPERM: break; case RCOK: if (ex_run_file(sp, fd, path)) return (1); break; } } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); /* Previous commands may have set the exrc option. */ if (O_ISSET(sp, O_EXRC)) { switch (exrc_isok(sp, &lsb, &fd, _PATH_NEXRC, 0, 0)) { case NOEXIST: if (exrc_isok(sp, &lsb, &fd, _PATH_EXRC, 0, 0) == RCOK) { if (lsb.st_dev != hsb.st_dev || lsb.st_ino != hsb.st_ino) { if (ex_run_file(sp, fd, _PATH_EXRC)) return (1); } else close(fd); } break; case NOPERM: break; case RCOK: if (lsb.st_dev != hsb.st_dev || lsb.st_ino != hsb.st_ino) { if (ex_run_file(sp, fd, _PATH_NEXRC)) return (1); } else close(fd); break; } /* Run the commands. */ if (EXCMD_RUNNING(sp->gp)) (void)ex_cmd(sp); if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) return (0); } return (0); } /* * ex_run_file -- * Set up a file of ex commands to run. */ static int ex_run_file(SCR *sp, int fd, char *name) { ARGS *ap[2], a; EXCMD cmd; ex_cinit(&cmd, C_SOURCE, 0, OOBLNO, OOBLNO, 0, ap); ex_cadd(&cmd, &a, name, strlen(name)); return (ex_sourcefd(sp, &cmd, fd)); } /* * ex_run_str -- * Set up a string of ex commands to run. * * PUBLIC: int ex_run_str(SCR *, char *, char *, size_t, int, int); */ int ex_run_str(SCR *sp, char *name, char *str, size_t len, int ex_flags, int nocopy) { GS *gp; EXCMD *ecp; gp = sp->gp; if (EXCMD_RUNNING(gp)) { CALLOC_RET(sp, ecp, 1, sizeof(EXCMD)); LIST_INSERT_HEAD(&gp->ecq, ecp, q); } else ecp = &gp->excmd; F_INIT(ecp, ex_flags ? E_BLIGNORE | E_NOAUTO | E_NOPRDEF | E_VLITONLY : 0); if (nocopy) ecp->cp = str; else if ((ecp->cp = v_strdup(sp, str, len)) == NULL) return (1); ecp->clen = len; if (name == NULL) ecp->if_name = NULL; else { if ((ecp->if_name = v_strdup(sp, name, strlen(name))) == NULL) return (1); ecp->if_lno = 1; F_SET(ecp, E_NAMEDISCARD); } return (0); } /* * exrc_isok -- * Open and check a .exrc file for source-ability. * * !!! * Historically, vi read the $HOME and local .exrc files if they were owned * by the user's real ID, or the "sourceany" option was set, regardless of * any other considerations. We no longer support the sourceany option as * it's a security problem of mammoth proportions. We require the system * .exrc file to be owned by root, the $HOME .exrc file to be owned by the * user's effective ID (or that the user's effective ID be root) and the * local .exrc files to be owned by the user's effective ID. In all cases, * the file cannot be writeable by anyone other than its owner. * * In O'Reilly ("Learning the VI Editor", Fifth Ed., May 1992, page 106), * it notes that System V release 3.2 and later has an option "[no]exrc". * The behavior is that local .exrc files are read only if the exrc option * is set. The default for the exrc option was off, so, by default, local * .exrc files were not read. The problem this was intended to solve was * that System V permitted users to give away files, so there's no possible * ownership or writeability test to ensure that the file is safe. * * POSIX 1003.2-1992 standardized exrc as an option. It required the exrc * option to be off by default, thus local .exrc files are not to be read * by default. The Rationale noted (incorrectly) that this was a change * to historic practice, but correctly noted that a default of off improves * system security. POSIX also required that vi check the effective user * ID instead of the real user ID, which is why we've switched from historic * practice. * * We initialize the exrc variable to off. If it's turned on by the system * or $HOME .exrc files, and the local .exrc file passes the ownership and * writeability tests, then we read it. This breaks historic 4BSD practice, * but it gives us a measure of security on systems where users can give away * files. */ static enum rc exrc_isok(SCR *sp, struct stat *sbp, int *fdp, char *path, int rootown, int rootid) { enum { ROOTOWN, OWN, WRITER } etype; uid_t euid; int nf1, nf2; char *a, *b, buf[PATH_MAX]; if ((*fdp = open(path, O_RDONLY)) < 0) { if (errno == ENOENT) /* This is the only case where ex_exrc() * should silently try the next file, for * example .exrc after .nexrc. */ return (NOEXIST); msgq_str(sp, M_SYSERR, path, "%s"); return (NOPERM); } if (fstat(*fdp, sbp)) { msgq_str(sp, M_SYSERR, path, "%s"); close(*fdp); return (NOPERM); } /* Check ownership permissions. */ euid = geteuid(); if (!(rootown && sbp->st_uid == 0) && !(rootid && euid == 0) && sbp->st_uid != euid) { etype = rootown ? ROOTOWN : OWN; goto denied; } /* Check writeability. */ if (sbp->st_mode & S_IWOTH) { etype = WRITER; goto denied; } struct group *grp_p; struct passwd *pwd_p; if (sbp->st_mode & S_IWGRP) { /* on system error (getgrgid or getpwnam return NULL) set etype to WRITER * and continue execution */ if( (grp_p = getgrgid(sbp->st_gid)) == NULL) { etype = WRITER; goto denied; } /* lookup the group members' uids for an uid different from euid */ while( ( *(grp_p->gr_mem) ) != NULL) { /* gr_mem is a null-terminated array */ if( (pwd_p = getpwnam(*(grp_p->gr_mem)++)) == NULL) { etype = WRITER; goto denied; } if(pwd_p->pw_uid != euid) { etype = WRITER; goto denied; } } } return (RCOK); denied: a = msg_print(sp, path, &nf1); if (strchr(path, '/') == NULL && getcwd(buf, sizeof(buf)) != NULL) { b = msg_print(sp, buf, &nf2); switch (etype) { case ROOTOWN: msgq(sp, M_ERR, "%s/%s: not sourced: not owned by you or root", b, a); break; case OWN: msgq(sp, M_ERR, "%s/%s: not sourced: not owned by you", b, a); break; case WRITER: msgq(sp, M_ERR, "%s/%s: not sourced: writable by a user other than the owner", b, a); break; } if (nf2) FREE_SPACE(sp, b, 0); } else switch (etype) { case ROOTOWN: msgq(sp, M_ERR, "%s: not sourced: not owned by you or root", a); break; case OWN: msgq(sp, M_ERR, "%s: not sourced: not owned by you", a); break; case WRITER: msgq(sp, M_ERR, "%s: not sourced: writable by a user other than the owner", a); break; } if (nf1) FREE_SPACE(sp, a, 0); close(*fdp); return (NOPERM); } ================================================ FILE: ex/ex_join.c ================================================ /* $OpenBSD: ex_join.c,v 1.8 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_join -- :[line [,line]] j[oin][!] [count] [flags] * Join lines. * * PUBLIC: int ex_join(SCR *, EXCMD *); */ int ex_join(SCR *sp, EXCMD *cmdp) { recno_t from, to; size_t blen, clen, len, tlen; int echar, extra, first; char *bp, *p, *tbp; NEEDFILE(sp, cmdp); from = cmdp->addr1.lno; (void)from; to = cmdp->addr2.lno; (void)to; /* Check for no lines to join. */ if (!db_exist(sp, from + 1)) { msgq(sp, M_ERR, "No following lines to join"); return (1); } GET_SPACE_RET(sp, bp, blen, 256); /* * The count for the join command was off-by-one, * historically, to other counts for other commands. */ if (F_ISSET(cmdp, E_ADDR_DEF) || cmdp->addrcnt == 1) ++cmdp->addr2.lno; clen = tlen = 0; for (first = 1, from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { /* * Get next line. Historic versions of vi allowed "10J" while * less than 10 lines from the end-of-file, so we do too. */ if (db_get(sp, from, 0, &p, &len)) { cmdp->addr2.lno = from - 1; break; } /* Empty lines just go away. */ if (len == 0) continue; /* * Get more space if necessary. Note, tlen isn't the length * of the new line, it's roughly the amount of space needed. * tbp - bp is the length of the new line. */ tlen += len + 2; ADD_SPACE_RET(sp, bp, blen, tlen); tbp = bp + clen; /* * Historic practice: * * If force specified, join without modification. * If the current line ends with whitespace, strip leading * whitespace from the joined line. * If the next line starts with a ), do nothing. * If the current line ends with ., insert two spaces. * Else, insert one space. * * One change -- add ? and ! to the list of characters for * which we insert two spaces. I expect that POSIX 1003.2 * will require this as well. * * Echar is the last character in the last line joined. */ extra = 0; if (!first && !FL_ISSET(cmdp->iflags, E_C_FORCE)) { if (isblank(echar)) for (; len && isblank(*p); --len, ++p); else if (p[0] != ')') { if (strchr(".?!", echar)) { *tbp++ = ' '; ++clen; extra = 1; } *tbp++ = ' '; ++clen; for (; len && isblank(*p); --len, ++p); } } if (len != 0) { memcpy(tbp, p, len); tbp += len; clen += len; echar = p[len - 1]; } else echar = ' '; /* * Historic practice for vi was to put the cursor at the first * inserted whitespace character, if there was one, or the * first character of the joined line, if there wasn't, or the * last character of the line if joined to an empty line. If * a count was specified, the cursor was moved as described * for the first line joined, ignoring subsequent lines. If * the join was a ':' command, the cursor was placed at the * first non-blank character of the line unless the cursor was * "attracted" to the end of line when the command was executed * in which case it moved to the new end of line. There are * probably several more special cases, but frankly, my dear, * I don't give a damn. This implementation puts the cursor * on the first inserted whitespace character, the first * character of the joined line, or the last character of the * line regardless. Note, if the cursor isn't on the joined * line (possible with : commands), it is reset to the starting * line. */ if (first) { sp->cno = (tbp - bp) - (1 + extra); first = 0; } else sp->cno = (tbp - bp) - len - (1 + extra); } sp->lno = cmdp->addr1.lno; /* Delete the joined lines. */ for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; to > from; --to) if (db_delete(sp, to)) goto err; /* If the original line changed, reset it. */ if (!first && db_set(sp, from, bp, tbp - bp)) { err: FREE_SPACE(sp, bp, blen); return (1); } FREE_SPACE(sp, bp, blen); sp->rptlines[L_JOINED] += (cmdp->addr2.lno - cmdp->addr1.lno) + 1; return (0); } ================================================ FILE: ex/ex_map.c ================================================ /* $OpenBSD: ex_map.c,v 1.9 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_map -- :map[!] [input] [replacement] * Map a key/string or display mapped keys. * * Historical note: * Historic vi maps were fairly bizarre, and likely to differ in * very subtle and strange ways from this implementation. Two * things worth noting are that vi would often hang or drop core * if the map was strange enough (ex: map X "xy$@x^V), or, simply * not work. One trick worth remembering is that if you put a * mark at the start of the map, e.g. map X mx"xy ...), or if you * put the map in a .exrc file, things would often work much better. * No clue why. * * PUBLIC: int ex_map(SCR *, EXCMD *); */ int ex_map(SCR *sp, EXCMD *cmdp) { seq_t stype; CHAR_T *input, *p; stype = FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND; switch (cmdp->argc) { case 0: if (seq_dump(sp, stype, 1) == 0) msgq(sp, M_INFO, stype == SEQ_INPUT ? "No input map entries" : "No command map entries"); return (0); case 2: input = cmdp->argv[0]->bp; break; default: abort(); } /* * If the mapped string is #[0-9]* (and wasn't quoted) then store the * function key mapping. If the screen specific routine has been set, * call it as well. Note, the SEQ_FUNCMAP type is persistent across * screen types, maybe the next screen type will get it right. */ if (input[0] == '#' && isdigit(input[1])) { for (p = input + 2; isdigit(*p); ++p); if (p[0] != '\0') goto nofunc; if (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_FUNCMAP | SEQ_USERDEF)) return (1); return (sp->gp->scr_fmap == NULL ? 0 : sp->gp->scr_fmap(sp, stype, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len)); } /* Some single keys may not be remapped in command mode. */ nofunc: if (stype == SEQ_COMMAND && input[1] == '\0') switch (KEY_VAL(sp, input[0])) { case K_COLON: case K_ESCAPE: case K_NL: msgq(sp, M_ERR, "The %s character may not be remapped", KEY_NAME(sp, input[0])); return (1); } return (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_USERDEF)); } /* * ex_unmap -- (:unmap[!] key) * Unmap a key. * * PUBLIC: int ex_unmap(SCR *, EXCMD *); */ int ex_unmap(SCR *sp, EXCMD *cmdp) { if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len, FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) { msgq_str(sp, M_INFO, cmdp->argv[0]->bp, "\"%s\" isn't currently mapped"); return (1); } return (0); } ================================================ FILE: ex/ex_mark.c ================================================ /* $OpenBSD: ex_mark.c,v 1.7 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_mark -- :mark char * :k char * Mark lines. * * * PUBLIC: int ex_mark(SCR *, EXCMD *); */ int ex_mark(SCR *sp, EXCMD *cmdp) { NEEDFILE(sp, cmdp); if (cmdp->argv[0]->len != 1) { msgq(sp, M_ERR, "Mark names must be a single character"); return (1); } return (mark_set(sp, cmdp->argv[0]->bp[0], &cmdp->addr1, 1)); } ================================================ FILE: ex/ex_mkexrc.c ================================================ /* $OpenBSD: ex_mkexrc.c,v 1.7 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "pathnames.h" #undef open /* * ex_mkexrc -- :mkexrc[!] [file] * * Create (or overwrite) a .exrc file with the current info. * * PUBLIC: int ex_mkexrc(SCR *, EXCMD *); */ int ex_mkexrc(SCR *sp, EXCMD *cmdp) { struct stat sb; FILE *fp; int fd, sverrno; char *fname; switch (cmdp->argc) { case 0: fname = _PATH_EXRC; break; case 1: fname = cmdp->argv[0]->bp; set_alt_name(sp, fname); break; default: abort(); } if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && !stat(fname, &sb)) { msgq_str(sp, M_ERR, fname, "%s exists, not written; use ! to override"); return (1); } /* Create with max permissions of rw-r--r--. */ if ((fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) { msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } if ((fp = fdopen(fd, "w")) == NULL) { sverrno = errno; (void)close(fd); goto e2; } if (seq_save(sp, fp, "abbreviate ", SEQ_ABBREV) || ferror(fp)) goto e1; if (seq_save(sp, fp, "map ", SEQ_COMMAND) || ferror(fp)) goto e1; if (seq_save(sp, fp, "map! ", SEQ_INPUT) || ferror(fp)) goto e1; if (opts_save(sp, fp) || ferror(fp)) goto e1; if (fclose(fp)) { sverrno = errno; goto e2; } msgq_str(sp, M_INFO, fname, "New exrc file: %s"); return (0); e1: sverrno = errno; (void)fclose(fp); e2: errno = sverrno; msgq_str(sp, M_SYSERR, fname, "%s"); return (1); } ================================================ FILE: ex/ex_move.c ================================================ /* $OpenBSD: ex_move.c,v 1.12 2025/08/23 21:02:10 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_copy -- :[line [,line]] co[py] line [flags] * Copy selected lines. * * PUBLIC: int ex_copy(SCR *, EXCMD *); */ int ex_copy(SCR *sp, EXCMD *cmdp) { CB cb; MARK fm1, fm2, m, tm; recno_t cnt; int rval; rval = 0; NEEDFILE(sp, cmdp); /* * It's possible to copy things into the area that's being * copied, e.g. "2,5copy3" is legitimate. Save the text to * a cut buffer. */ fm1 = cmdp->addr1; fm2 = cmdp->addr2; memset(&cb, 0, sizeof(cb)); TAILQ_INIT(&cb.textq); for (cnt = fm1.lno; cnt <= fm2.lno; ++cnt) if (cut_line(sp, cnt, 0, CUT_LINE_TO_EOL, &cb)) { rval = 1; goto err; } cb.flags |= CB_LMODE; /* Put the text into place. */ tm.lno = cmdp->lineno; tm.cno = 0; if (put(sp, &cb, NULL, &tm, &m, 1, 1)) rval = 1; else { /* * Copy puts the cursor on the last line copied. The cursor * returned by the put routine is the first line put, not the * last, because that's the historic semantic of vi. */ cnt = (fm2.lno - fm1.lno) + 1; sp->lno = m.lno + (cnt - 1); sp->cno = 0; } err: text_lfree(&cb.textq); return (rval); } /* * ex_move -- :[line [,line]] mo[ve] line * Move selected lines. * * PUBLIC: int ex_move(SCR *, EXCMD *); */ int ex_move(SCR *sp, EXCMD *cmdp) { LMARK *lmp; MARK fm1, fm2; recno_t cnt, diff, fl, tl, mfl, mtl; size_t blen, len; int mark_reset; char *bp, *p; NEEDFILE(sp, cmdp); /* * It's not possible to move things into the area that's being * moved. */ fm1 = cmdp->addr1; fm2 = cmdp->addr2; if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) { msgq(sp, M_ERR, "Destination line is inside move range"); return (1); } /* * Log the positions of any marks in the to-be-deleted lines. This * has to work with the logging code. What happens is that we log * the old mark positions, make the changes, then log the new mark * positions. Then the marks end up in the right positions no matter * which way the log is traversed. * * XXX * Reset the MARK_USERSET flag so that the log can undo the mark. * This isn't very clean, and should probably be fixed. */ fl = fm1.lno; tl = cmdp->lineno; /* Log the old positions of the marks. */ mark_reset = 0; LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno >= fl && lmp->lno <= tl) { mark_reset = 1; F_CLR(lmp, MARK_USERSET); (void)log_mark(sp, lmp); } /* Get memory for the copy. */ GET_SPACE_RET(sp, bp, blen, 256); /* Move the lines. */ diff = (fm2.lno - fm1.lno) + 1; if (tl > fl) { /* Destination > source. */ mfl = tl - diff; mtl = tl; for (cnt = diff; cnt--;) { if (db_get(sp, fl, DBG_FATAL, &p, &len)) return (1); BINC_RET(sp, bp, blen, len); memcpy(bp, p, len); if (db_append(sp, 1, tl, bp, len)) return (1); if (mark_reset) LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno == fl) lmp->lno = tl + 1; if (db_delete(sp, fl)) return (1); } } else { /* Destination < source. */ mfl = tl; mtl = tl + diff; for (cnt = diff; cnt--;) { if (db_get(sp, fl, DBG_FATAL, &p, &len)) return (1); BINC_RET(sp, bp, blen, len); memcpy(bp, p, len); if (db_append(sp, 1, tl++, bp, len)) return (1); if (mark_reset) LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno == fl) lmp->lno = tl; ++fl; if (db_delete(sp, fl)) return (1); } } FREE_SPACE(sp, bp, blen); sp->lno = tl; /* Last line moved. */ sp->cno = 0; /* Log the new positions of the marks. */ if (mark_reset) LIST_FOREACH(lmp, &sp->ep->marks, q) if (lmp->name != ABSMARK1 && lmp->lno >= mfl && lmp->lno <= mtl) (void)log_mark(sp, lmp); sp->rptlines[L_MOVED] += diff; return (0); } ================================================ FILE: ex/ex_open.c ================================================ /* $OpenBSD: ex_open.c,v 1.7 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include "../common/common.h" #undef open /* * ex_open -- :[line] o[pen] [/pattern/] [flags] * * Switch to single line "open" mode. * * PUBLIC: int ex_open(SCR *, EXCMD *); */ int ex_open(SCR *sp, EXCMD *cmdp) { /* If open option off, disallow open command. */ if (!O_ISSET(sp, O_OPEN)) { msgq(sp, M_ERR, "The open command requires that the open option be set"); return (1); } msgq(sp, M_ERR, "The open command is not yet implemented"); return (1); } ================================================ FILE: ex/ex_preserve.c ================================================ /* $OpenBSD: ex_preserve.c,v 1.7 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_preserve -- :pre[serve] * Push the file to recovery. * * PUBLIC: int ex_preserve(SCR *, EXCMD *); */ int ex_preserve(SCR *sp, EXCMD *cmdp) { recno_t lno; NEEDFILE(sp, cmdp); if (!F_ISSET(sp->ep, F_RCV_ON)) { msgq(sp, M_ERR, "Preservation of this file not possible"); return (1); } /* If recovery not initialized, do so. */ if (F_ISSET(sp->ep, F_FIRSTMODIFY) && rcv_init(sp)) return (1); /* Force the file to be read in, in case it hasn't yet. */ if (db_last(sp, &lno)) return (1); /* Sync to disk. */ if (rcv_sync(sp, RCV_SNAPSHOT)) return (1); msgq(sp, M_INFO, "File preserved"); return (0); } /* * ex_recover -- :rec[over][!] file * Recover the file. * * PUBLIC: int ex_recover(SCR *, EXCMD *); */ int ex_recover(SCR *sp, EXCMD *cmdp) { ARGS *ap; FREF *frp; ap = cmdp->argv[0]; /* Set the alternate file name. */ set_alt_name(sp, ap->bp); /* * Check for modifications. Autowrite did not historically * affect :recover. */ if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); /* Get a file structure for the file. */ if ((frp = file_add(sp, ap->bp)) == NULL) return (1); /* Set the recover bit. */ F_SET(frp, FR_RECOVER); /* Switch files. */ if (file_init(sp, frp, NULL, FS_SETALT | (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) return (1); F_SET(sp, SC_FSWITCH); return (0); } ================================================ FILE: ex/ex_print.c ================================================ /* $OpenBSD: ex_print.c,v 1.13 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" static int ex_prchars(SCR *, const char *, size_t *, size_t, unsigned int, int); /* * ex_list -- :[line [,line]] l[ist] [count] [flags] * * Display the addressed lines such that the output is unambiguous. * * PUBLIC: int ex_list(SCR *, EXCMD *); */ int ex_list(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_LIST)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_number -- :[line [,line]] nu[mber] [count] [flags] * * Display the addressed lines with a leading line number. * * PUBLIC: int ex_number(SCR *, EXCMD *); */ int ex_number(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_HASH)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_pr -- :[line [,line]] p[rint] [count] [flags] * * Display the addressed lines. * * PUBLIC: int ex_pr(SCR *, EXCMD *); */ int ex_pr(SCR *sp, EXCMD *cmdp) { if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags)) return (1); sp->lno = cmdp->addr2.lno; sp->cno = cmdp->addr2.cno; return (0); } /* * ex_print -- * Print the selected lines. * * PUBLIC: int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); */ int ex_print(SCR *sp, EXCMD *cmdp, MARK *fp, MARK *tp, u_int32_t flags) { recno_t from, to; size_t col, len; char *p, buf[16]; NEEDFILE(sp, cmdp); for (from = fp->lno, to = tp->lno; from <= to; ++from) { col = 0; /* * Display the line number. The %6 format is specified * by POSIX 1003.2, and is almost certainly large enough. * Check, though, just in case. */ if (LF_ISSET(E_C_HASH)) { if (from <= 999999) { snprintf(buf, sizeof(buf), "%6lu ", (unsigned long)from); p = buf; } else p = "TOOBIG "; if (ex_prchars(sp, p, &col, 8, 0, 0)) return (1); } /* * Display the line. The format for E_C_PRINT isn't very good, * especially in handling end-of-line tabs, but they're almost * backward compatible. */ if (db_get(sp, from, DBG_FATAL, &p, &len)) return (1); if (len == 0 && !LF_ISSET(E_C_LIST)) (void)ex_puts(sp, "\n"); else if (ex_ldisplay(sp, p, len, col, flags)) return (1); if (INTERRUPTED(sp)) break; } return (0); } /* * ex_ldisplay -- * Display a line without any preceding number. * * PUBLIC: int ex_ldisplay(SCR *, const char *, size_t, size_t, unsigned int); */ int ex_ldisplay(SCR *sp, const char *p, size_t len, size_t col, unsigned int flags) { if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0)) return (1); if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) { p = "$"; if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0)) return (1); } if (!INTERRUPTED(sp)) (void)ex_puts(sp, "\n"); return (0); } /* * ex_scprint -- * Display a line for the substitute with confirmation routine. * * PUBLIC: int ex_scprint(SCR *, MARK *, MARK *); */ int ex_scprint(SCR *sp, MARK *fp, MARK *tp) { const char *p; size_t col, len; col = 0; if (O_ISSET(sp, O_NUMBER)) { p = " "; if (ex_prchars(sp, p, &col, 8, 0, 0)) return (1); } if (db_get(sp, fp->lno, DBG_FATAL, (char **)&p, &len)) return (1); if (ex_prchars(sp, p, &col, fp->cno, 0, ' ')) return (1); p += fp->cno; if (ex_prchars(sp, p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^')) return (1); if (INTERRUPTED(sp)) return (1); p = "[ynq]"; if (ex_prchars(sp, p, &col, 5, 0, 0)) return (1); (void)ex_fflush(sp); return (0); } /* * ex_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const char *p, size_t *colp, size_t len, unsigned int flags, int repeatc) { CHAR_T ch, *kp; size_t col, tlen, ts; if (O_ISSET(sp, O_LIST)) LF_SET(E_C_LIST); ts = O_VAL(sp, O_TABSTOP); for (col = *colp; len--;) if ((ch = *p++) == '\t' && !LF_ISSET(E_C_LIST)) for (tlen = ts - col % ts; col < sp->cols && tlen--; ++col) { (void)ex_printf(sp, "%c", repeatc ? repeatc : ' '); if (INTERRUPTED(sp)) goto intr; } else { kp = KEY_NAME(sp, ch); tlen = KEY_LEN(sp, ch); if (!repeatc && col + tlen < sp->cols) { (void)ex_puts(sp, kp); col += tlen; } else for (; tlen--; ++kp, ++col) { if (col == sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } (void)ex_printf(sp, "%c", repeatc ? repeatc : *kp); if (INTERRUPTED(sp)) goto intr; } } intr: *colp = col; return (0); } /* * ex_printf -- * Ex's version of printf. * * PUBLIC: int ex_printf(SCR *, const char *, ...); */ int ex_printf(SCR *sp, const char *fmt, ...) { EX_PRIVATE *exp; va_list ap; size_t n; exp = EXP(sp); va_start(ap, fmt); n = vsnprintf(exp->obp + exp->obp_len, sizeof(exp->obp) - exp->obp_len, fmt, ap); va_end(ap); if (n >= sizeof(exp->obp) - exp->obp_len) n = sizeof(exp->obp) - exp->obp_len - 1; exp->obp_len += n; /* Flush when reach a or half the buffer. */ if (exp->obp[exp->obp_len - 1] == '\n' || exp->obp_len > sizeof(exp->obp) / 2) (void)ex_fflush(sp); return (n); } /* * ex_puts -- * Ex's version of puts. * * PUBLIC: int ex_puts(SCR *, const char *); */ int ex_puts(SCR *sp, const char *str) { EX_PRIVATE *exp; int doflush, n; exp = EXP(sp); /* Flush when reach a or the end of the buffer. */ for (doflush = n = 0; *str != '\0'; ++n) { if (exp->obp_len > sizeof(exp->obp)) (void)ex_fflush(sp); if ((exp->obp[exp->obp_len++] = *str++) == '\n') doflush = 1; } if (doflush) (void)ex_fflush(sp); return (n); } /* * ex_fflush -- * Ex's version of fflush. * * PUBLIC: int ex_fflush(SCR *sp); */ int ex_fflush(SCR *sp) { EX_PRIVATE *exp; exp = EXP(sp); if (exp->obp_len != 0) { sp->gp->scr_msg(sp, M_NONE, exp->obp, exp->obp_len); exp->obp_len = 0; } return (0); } ================================================ FILE: ex/ex_put.c ================================================ /* $OpenBSD: ex_put.c,v 1.7 2025/08/23 21:02:10 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_put -- [line] pu[t] [buffer] * Append a cut buffer into the file. * * PUBLIC: int ex_put(SCR *, EXCMD *); */ int ex_put(SCR *sp, EXCMD *cmdp) { MARK m; NEEDFILE(sp, cmdp); m.lno = sp->lno; m.cno = sp->cno; if (put(sp, NULL, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &m, 1, 1)) return (1); sp->lno = m.lno; sp->cno = m.cno; return (0); } ================================================ FILE: ex/ex_quit.c ================================================ /* $OpenBSD: ex_quit.c,v 1.5 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_quit -- :quit[!] * Quit. * * PUBLIC: int ex_quit(SCR *, EXCMD *); */ int ex_quit(SCR *sp, EXCMD *cmdp) { int force; force = FL_ISSET(cmdp->iflags, E_C_FORCE); /* Check for file modifications, or more files to edit. */ if (file_m2(sp, force) || ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } ================================================ FILE: ex/ex_read.c ================================================ /* $OpenBSD: ex_read.c,v 1.14 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #undef open /* * ex_read -- :read [file] * :read [!cmd] * Read from a file or utility. * * !!! * Historical vi wouldn't undo a filter read, for no apparent reason. * * PUBLIC: int ex_read(SCR *, EXCMD *); */ int ex_read(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg, *name; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; recno_t nlines = 0; size_t arglen; int argc, rval; char *p; gp = sp->gp; /* * 0 args: read the current pathname. * 1 args: check for "read !arg". */ switch (cmdp->argc) { case 0: which = R_ARG; arg = NULL; /* unused */ arglen = 0; /* unused */ break; case 1: arg = cmdp->argv[0]->bp; arglen = cmdp->argv[0]->len; if (*arg == '!') { ++arg; --arglen; which = R_FILTER; /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } } else which = R_EXPANDARG; break; default: abort(); /* NOTREACHED */ } /* Load a temporary file if no file being edited. */ if (sp->ep == NULL) { if ((frp = file_add(sp, NULL)) == NULL) return (1); if (file_init(sp, frp, NULL, 0)) return (1); } switch (which) { case R_FILTER: /* * File name and bang expand the user's argument. If * we don't get an additional argument, it's illegal. */ argc = cmdp->argc; if (argv_exp1(sp, cmdp, arg, arglen, 1)) return (1); if (argc == cmdp->argc) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } argc = cmdp->argc - 1; /* Set the last bang command. */ exp = EXP(sp); free(exp->lastbcomm); if ((exp->lastbcomm = strdup(cmdp->argv[argc]->bp)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* * Vi redisplayed the user's argument if it changed, ex * always displayed a !, plus the user's argument if it * changed. */ if (F_ISSET(sp, SC_VI)) { if (F_ISSET(cmdp, E_MODIFY)) (void)vs_update(sp, "!", cmdp->argv[argc]->bp); } else { if (F_ISSET(cmdp, E_MODIFY)) (void)ex_printf(sp, "!%s\n", cmdp->argv[argc]->bp); else (void)ex_puts(sp, "!\n"); (void)ex_fflush(sp); } /* * Historically, filter reads as the first ex command didn't * wait for the user. If SC_SCR_EXWROTE not already set, set * the don't-wait flag. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); /* * Switch into ex canonical mode. The reason to restore the * original terminal modes for read filters is so that users * can do things like ":r! cat /dev/tty". * * !!! * We do not output an extra , so that we don't touch * the screen on a normal read. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON_F); return (1); } /* * !!! * Historically, the read command doesn't switch to * the alternate X11 xterm screen, if doing a filter * read -- don't set SA_ALTERNATE. */ F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } if (ex_filter(sp, cmdp, &cmdp->addr1, NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) return (1); /* The filter version of read set the autoprint flag. */ F_SET(cmdp, E_AUTOPRINT); /* * If in vi mode, move to the first nonblank. Might have * switched into ex mode, so saved the original SC_VI value. */ sp->lno = rm.lno; if (F_ISSET(sp, SC_VI)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } return (0); case R_ARG: name = sp->frp->name; break; case R_EXPANDARG: if (argv_exp2(sp, cmdp, arg, arglen)) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: name = cmdp->argv[1]->bp; /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = v_strdup(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } else set_alt_name(sp, name); break; default: ex_emsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); return (1); } break; default: abort(); /* NOTREACHED */ } /* * !!! * Historically, vi did not permit reads from non-regular files, nor * did it distinguish between "read !" and "read!", so there was no * way to "force" it. We permit reading from named pipes too, since * they didn't exist when the original implementation of vi was done * and they seem a reasonable addition. */ if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { msgq_str(sp, M_SYSERR, name, "%s"); return (1); } if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { (void)fclose(fp); msgq(sp, M_ERR, "Only regular files and named pipes may be read"); return (1); } /* Try and get a lock. */ if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL) msgq(sp, M_ERR, "%s: read lock was unavailable", name); rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); /* * In vi, set the cursor to the first line read in, if anything read * in, otherwise, the address. (Historic vi set it to the line after * the address regardless, but since that line may not exist we don't * bother.) * * In ex, set the cursor to the last line read in, if anything read in, * otherwise, the address. */ if (F_ISSET(sp, SC_VI)) { sp->lno = cmdp->addr1.lno; if (nlines) ++sp->lno; } else sp->lno = cmdp->addr1.lno + nlines; return (rval); } /* * ex_readfp -- * Read lines into the file. * * PUBLIC: int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int); */ int ex_readfp(SCR *sp, char *name, FILE *fp, MARK *fm, recno_t *nlinesp, int silent) { EX_PRIVATE *exp; GS *gp; recno_t lcnt, lno; size_t len; unsigned long ccnt; /* XXX: can't print off_t portably. */ int nf, rval; char *p; gp = sp->gp; exp = EXP(sp); /* * Add in the lines from the output. Insertion starts at the line * following the address. */ ccnt = 0; lcnt = 0; p = "Reading..."; for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) { if ((lcnt + 1) % INTERRUPT_CHECK == 0) { if (INTERRUPTED(sp)) break; if (!silent) { gp->scr_busy(sp, p, p == NULL ? BUSY_UPDATE : BUSY_ON); p = NULL; } } if (db_append(sp, 1, lno, exp->ibp, len)) goto err; ccnt += len; } if (ferror(fp) || fclose(fp)) goto err; /* Return the number of lines read in. */ if (nlinesp != NULL) *nlinesp = lcnt; if (!silent) { p = msg_print(sp, name, &nf); msgq(sp, M_INFO, "%s: %'lu lines, %'lu characters", p, lcnt, ccnt); if (nf) FREE_SPACE(sp, p, 0); } rval = 0; if (0) { err: msgq_str(sp, M_SYSERR, name, "%s"); (void)fclose(fp); rval = 1; } if (!silent) gp->scr_busy(sp, NULL, BUSY_OFF); return (rval); } ================================================ FILE: ex/ex_screen.c ================================================ /* $OpenBSD: ex_screen.c,v 1.10 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_bg -- :bg * Hide the screen. * * PUBLIC: int ex_bg(SCR *, EXCMD *); */ int ex_bg(SCR *sp, EXCMD *cmdp) { return (vs_bg(sp)); } /* * ex_fg -- :fg [file] * Show the screen. * * PUBLIC: int ex_fg(SCR *, EXCMD *); */ int ex_fg(SCR *sp, EXCMD *cmdp) { SCR *nsp; int newscreen; newscreen = F_ISSET(cmdp, E_NEWSCREEN); if (vs_fg(sp, &nsp, cmdp->argc ? cmdp->argv[0]->bp : NULL, newscreen)) return (1); /* Set up the switch. */ if (newscreen) { sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); } return (0); } /* * ex_resize -- :resize [+-]rows * Change the screen size. * * PUBLIC: int ex_resize(SCR *, EXCMD *); */ int ex_resize(SCR *sp, EXCMD *cmdp) { adj_t adj; switch (FL_ISSET(cmdp->iflags, E_C_COUNT | E_C_COUNT_NEG | E_C_COUNT_POS)) { case E_C_COUNT: adj = A_SET; break; case E_C_COUNT | E_C_COUNT_NEG: adj = A_DECREASE; break; case E_C_COUNT | E_C_COUNT_POS: adj = A_INCREASE; break; default: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } return (vs_resize(sp, cmdp->count, adj)); } /* * ex_sdisplay -- * Display the list of screens. * * PUBLIC: int ex_sdisplay(SCR *); */ int ex_sdisplay(SCR *sp) { GS *gp; SCR *tsp; int cnt, col, len, sep; gp = sp->gp; if (TAILQ_EMPTY(&gp->hq)) { msgq(sp, M_INFO, "No background screens to display"); return (0); } col = len = sep = 0; cnt = 1; TAILQ_FOREACH(tsp, &gp->hq, q) { if (INTERRUPTED(sp)) break; col += len = strlen(tsp->frp->name) + sep; if (col >= sp->cols - 1) { col = len; sep = 0; (void)ex_puts(sp, "\n"); } else if (cnt != 1) { sep = 1; (void)ex_puts(sp, " "); } (void)ex_puts(sp, tsp->frp->name); ++cnt; } if (!INTERRUPTED(sp)) (void)ex_puts(sp, "\n"); return (0); } ================================================ FILE: ex/ex_script.c ================================================ /* $OpenBSD: ex_script.c,v 1.27 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * This code is derived from software contributed to Berkeley by * Brian Hirt. * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__solaris__) # define __EXTENSIONS__ # include # include #endif /* if defined(__solaris__) */ #if defined(__OpenBSD__) || defined(__NetBSD__) # include # include # include #else # include # include # include #endif /* if defined(__OpenBSD__) || defined(__NetBSD__) */ #ifndef __FreeBSD__ # ifdef _AIX # include # else # ifndef __OpenBSD__ # if ( !defined(__APPLE__) && !defined(__MACH__) && \ !defined(__NetBSD__) ) # if !defined(__solaris__) && !defined(__illumos__) # include # endif /* if !defined(__solaris__) && !defined(__illumos__) */ # endif /* if ( !defined(__APPLE__) && !defined(__MACH__) && \ !defined(__NetBSD__) ) */ # endif /* ifndef __OpenBSD__ */ # endif /* ifdef _AIX */ #else # include #endif /* ifndef __FreeBSD__ */ #include "../common/common.h" #include "../vi/vi.h" #include "script.h" #include "pathnames.h" #if defined(__OpenBSD__) || defined(__NetBSD__) || \ ( defined(__APPLE__) && defined(__MACH__) ) int openpty(int *, int *, char *, struct termios *, struct winsize *); #endif /* defined(__OpenBSD__) || defined(__NetBSD__) || ( defined(__APPLE__) && defined(__MACH__) ) */ #if defined(_AIX) || defined(__illumos__) || defined(__solaris__) # define HAVE_SYS5_PTY #endif /* if defined(_AIX) || defined(__illumos__) || defined(__solaris__) */ #ifdef HAVE_SYS5_PTY # include static int ptys_open(int fdm, char *pts_name); static int ptym_open(char *pts_name); static int sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp); #endif /* ifdef HAVE_SYS5_PTY */ static void sscr_check(SCR *); static int sscr_getprompt(SCR *); static int sscr_init(SCR *); static int sscr_insert(SCR *); static int sscr_matchprompt(SCR *, char *, size_t, size_t *); static int sscr_setprompt(SCR *, char *, size_t); /* * ex_script -- : sc[ript][!] [file] * Switch to script mode. * * PUBLIC: int ex_script(SCR *, EXCMD *); */ int ex_script(SCR *sp, EXCMD *cmdp) { /* Vi only command. */ if (!F_ISSET(sp, SC_VI)) { msgq(sp, M_ERR, "The script command is only available in vi mode"); return (1); } /* Switch to the new file. */ if (cmdp->argc != 0 && ex_edit(sp, cmdp)) return (1); /* Create the shell, figure out the prompt. */ if (sscr_init(sp)) return (1); return (0); } /* * sscr_init -- * Create a pty setup for a shell. */ static int sscr_init(SCR *sp) { SCRIPT *sc; char *sh, *sh_path; /* We're going to need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); MALLOC_RET(sp, sc, sizeof(SCRIPT)); sp->script = sc; sc->sh_prompt = NULL; sc->sh_prompt_len = 0; /* * There are two different processes running through this code. * They are the shell and the parent. */ sc->sh_master = sc->sh_slave = -1; if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } /* * Turn off output postprocessing and echo. */ sc->sh_term.c_oflag &= ~OPOST; sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } #ifdef HAVE_SYS5_PTY # ifdef TIOCGWINSZ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "tcgetattr"); goto err; } if (sscr_pty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "pty"); goto err; } # else if (sscr_pty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) { msgq(sp, M_SYSERR, "pty"); goto err; } # endif /* ifdef TIOCGWINSZ */ #else if (openpty(&sc->sh_master, &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { msgq(sp, M_SYSERR, "pty"); goto err; } #endif /* HAVE_SYS5_PTY */ switch (sc->sh_pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); err: if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); return (1); case 0: /* Utility. */ /* * XXX * So that shells that do command line editing turn it off. */ if (setenv("TERM", "emacs", 1) == -1 || setenv("TERMCAP", "emacs:", 1) == -1 || setenv("EMACS", "t", 1) == -1) _exit(126); (void)setsid(); #ifdef TIOCSCTTY /* * 4.4BSD allocates a controlling terminal using the TIOCSCTTY * ioctl, not by opening a terminal device file. POSIX 1003.1 * doesn't define a portable way to do this. */ (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); #endif /* ifdef TIOCSCTTY */ (void)close(sc->sh_master); (void)dup2(sc->sh_slave, STDIN_FILENO); (void)dup2(sc->sh_slave, STDOUT_FILENO); (void)dup2(sc->sh_slave, STDERR_FILENO); (void)close(sc->sh_slave); /* Assumes that all shells have -i. */ sh_path = O_STR(sp, O_SHELL); if ((sh = strrchr(sh_path, '/')) == NULL) sh = sh_path; else ++sh; execl(sh_path, sh, "-i", (char *)NULL); msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); _exit(127); default: /* Parent. */ break; } if (sscr_getprompt(sp)) return (1); F_SET(sp, SC_SCRIPT); F_SET(sp->gp, G_SCRWIN); return (0); } /* * sscr_getprompt -- * Eat lines printed by the shell until a line with no trailing * carriage return comes; set the prompt from that line. */ static int sscr_getprompt(SCR *sp) { CHAR_T *endp, *p, *t, buf[1024]; SCRIPT *sc; struct pollfd pfd[1]; recno_t lline; size_t llen, len; unsigned int value; int nr; endp = buf; len = sizeof(buf); (void)len; /* Wait up to a second for characters to read. */ sc = sp->script; pfd[0].fd = sc->sh_master; pfd[0].events = POLLIN; switch (poll(pfd, 1, 5 * 1000)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "poll"); goto prompterr; case 0: /* Timeout */ msgq(sp, M_ERR, "Error: timed out"); goto prompterr; default: /* Characters to read. */ break; } /* Read the characters. */ more: len = sizeof(buf) - (endp - buf); switch (nr = read(sc->sh_master, endp, len)) { case 0: /* EOF. */ msgq(sp, M_ERR, "Error: shell: EOF"); goto prompterr; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto prompterr; default: endp += nr; break; } /* If any complete lines, push them into the file. */ for (p = t = buf; p < endp; ++p) { value = KEY_VAL(sp, *p); if (value == K_CR || value == K_NL) { if (db_last(sp, &lline) || db_append(sp, 0, lline, t, p - t)) goto prompterr; t = p + 1; } } if (p > buf) { memmove(buf, t, endp - t); endp = buf + (endp - t); } if (endp == buf) goto more; /* Wait up 1/10 of a second to make sure that we got it all. */ switch (poll(pfd, 1, 100)) { case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "poll"); goto prompterr; case 0: /* Timeout */ break; default: /* Characters to read. */ goto more; } /* Timed out, so theoretically we have a prompt. */ llen = endp - buf; (void)llen; endp = buf; (void)endp; /* Append the line into the file. */ if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) { prompterr: sscr_end(sp); return (1); } return (sscr_setprompt(sp, buf, llen)); } /* * sscr_exec -- * Take a line and hand it off to the shell. * * PUBLIC: int sscr_exec(SCR *, recno_t); */ int sscr_exec(SCR *sp, recno_t lno) { SCRIPT *sc; recno_t last_lno; size_t blen, len, last_len, tlen; int isempty, matchprompt, nw, rval; char *bp, *p; /* If there's a prompt on the last line, append the command. */ if (sp == NULL) return (1); if (db_last(sp, &last_lno)) return (1); if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len)) return (1); if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { matchprompt = 1; GET_SPACE_RET(sp, bp, blen, last_len + 128); if (bp != NULL) memmove(bp, p, last_len); } else matchprompt = 0; /* Get something to execute. */ if (db_eget(sp, lno, &p, &len, &isempty)) { if (isempty) goto empty; goto err1; } /* Empty lines aren't interesting. */ if (len == 0) goto empty; /* Delete any prompt. */ if (sscr_matchprompt(sp, p, len, &tlen)) { if (tlen == len) { empty: msgq(sp, M_BERR, "No command to execute"); goto err1; } p += (len - tlen); len = tlen; } /* Push the line to the shell. */ sc = sp->script; if ((nw = write(sc->sh_master, p, len)) != len) goto err2; rval = 0; if (write(sc->sh_master, "\n", 1) != 1) { err2: if (nw == 0) errno = EIO; msgq(sp, M_SYSERR, "shell"); goto err1; } if (matchprompt) { ADD_SPACE_RET(sp, bp, blen, last_len + len); if (bp != NULL) { memmove(bp + last_len, p, len); if (db_set(sp, last_lno, bp, last_len + len)) err1: rval = 1; } } if (matchprompt) FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_check_input - * Check for input from command input or scripting windows. * * PUBLIC: int sscr_check_input(SCR *sp); */ int sscr_check_input(SCR *sp) { GS *gp; SCR *tsp; struct pollfd *pfd; int nfds, rval; gp = sp->gp; rval = 0; /* Allocate space for pfd. */ nfds = 1; TAILQ_FOREACH(tsp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) nfds++; pfd = calloc(nfds, sizeof(struct pollfd)); if (pfd == NULL) { msgq(sp, M_SYSERR, "malloc"); return (1); } /* Setup events bitmasks. */ pfd[0].fd = STDIN_FILENO; pfd[0].events = POLLIN; nfds = 1; TAILQ_FOREACH(tsp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { pfd[nfds].fd = sp->script->sh_master; pfd[nfds].events = POLLIN; nfds++; } loop: /* Check for input. */ switch (poll(pfd, nfds, INFTIM)) { case -1: msgq(sp, M_SYSERR, "poll"); rval = 1; /* FALLTHROUGH */ case 0: goto done; default: break; } /* Only insert from the scripting windows if no command input */ if (!(pfd[0].revents & POLLIN)) { nfds = 1; TAILQ_FOREACH(tsp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { if ((pfd[nfds].revents & POLLHUP) && sscr_end(sp)) goto done; if ((pfd[nfds].revents & POLLIN) && sscr_insert(sp)) goto done; nfds++; } goto loop; } done: free(pfd); return (rval); } /* * sscr_input -- * Read any waiting shell input. * * PUBLIC: int sscr_input(SCR *); */ int sscr_input(SCR *sp) { GS *gp; struct pollfd *pfd; int nfds, rval; gp = sp->gp; rval = 0; /* Allocate space for pfd. */ nfds = 0; TAILQ_FOREACH(sp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) nfds++; if (nfds == 0) return (0); pfd = calloc(nfds, sizeof(struct pollfd)); if (pfd == NULL) { msgq(sp, M_SYSERR, "malloc"); return (1); } /* Setup events bitmasks. */ nfds = 0; TAILQ_FOREACH(sp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { pfd[nfds].fd = sp->script->sh_master; pfd[nfds].events = POLLIN; nfds++; } loop: /* Check for input. */ switch (poll(pfd, nfds, 0)) { case -1: msgq(sp, M_SYSERR, "poll"); rval = 1; /* FALLTHROUGH */ case 0: goto done; default: break; } /* Read the input. */ nfds = 0; TAILQ_FOREACH(sp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { if ((pfd[nfds].revents & POLLHUP) && sscr_end(sp)) goto done; if ((pfd[nfds].revents & POLLIN) && sscr_insert(sp)) goto done; nfds++; } goto loop; done: free(pfd); return (rval); } /* * sscr_insert -- * Take a line from the shell and insert it into the file. */ static int sscr_insert(SCR *sp) { CHAR_T *endp, *p, *t; SCRIPT *sc; struct pollfd pfd[1]; recno_t lno; size_t blen, len, tlen; unsigned int value; int nr, rval; char *bp; /* Find out where the end of the file is. */ if (db_last(sp, &lno)) return (1); #define MINREAD 1024 GET_SPACE_RET(sp, bp, blen, MINREAD); endp = bp; /* Read the characters. */ rval = 1; sc = sp->script; more: switch (nr = read(sc->sh_master, endp, MINREAD)) { case 0: /* EOF; shell just exited. */ sscr_end(sp); rval = 0; goto ret; case -1: /* Error or interrupt. */ msgq(sp, M_SYSERR, "shell"); goto ret; default: endp += nr; break; } /* Append the lines into the file. */ for (p = t = bp; p < endp; ++p) { value = KEY_VAL(sp, *p); if (value == K_CR || value == K_NL) { len = p - t; if (db_append(sp, 1, lno++, t, len)) goto ret; t = p + 1; } } if (p > t) { len = p - t; /* * If the last thing from the shell isn't another prompt, wait * up to 1/10 of a second for more stuff to show up, so that * we don't break the output into two separate lines. Don't * want to hang indefinitely because some program is hanging, * confused the shell, or whatever. */ if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { pfd[0].fd = sc->sh_master; pfd[0].events = POLLIN; if (poll(pfd, 1, 100) > 0) { memmove(bp, t, len); endp = bp + len; goto more; } } if (sscr_setprompt(sp, t, len)) return (1); if (db_append(sp, 1, lno++, t, len)) goto ret; } /* The cursor moves to EOF. */ sp->lno = lno; sp->cno = len ? len - 1 : 0; rval = vs_refresh(sp, 1); ret: FREE_SPACE(sp, bp, blen); return (rval); } /* * sscr_setprompt -- * * Set the prompt to the last line we got from the shell. * */ static int sscr_setprompt(SCR *sp, char *buf, size_t len) { SCRIPT *sc; sc = sp->script; free(sc->sh_prompt); MALLOC(sp, sc->sh_prompt, len + 1); if (sc->sh_prompt == NULL) { sscr_end(sp); return (1); } memmove(sc->sh_prompt, buf, len); sc->sh_prompt_len = len; sc->sh_prompt[len] = '\0'; return (0); } /* * sscr_matchprompt -- * Check to see if a line matches the prompt. Nul's indicate * parts that can change, in both content and size. */ static int sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp) { SCRIPT *sc; size_t prompt_len; char *pp; sc = sp->script; if (line_len < (prompt_len = sc->sh_prompt_len)) return (0); for (pp = sc->sh_prompt; prompt_len && line_len; --prompt_len, --line_len) { if (*pp == '\0') { for (; prompt_len && *pp == '\0'; --prompt_len, ++pp); if (!prompt_len) return (0); for (; line_len && *lp != *pp; --line_len, ++lp); if (!line_len) return (0); } if (*pp++ != *lp++) break; } if (prompt_len) return (0); if (lenp != NULL) *lenp = line_len; return (1); } /* * sscr_end -- * End the pipe to a shell. * * PUBLIC: int sscr_end(SCR *); */ int sscr_end(SCR *sp) { SCRIPT *sc; if ((sc = sp->script) == NULL) return (0); /* Turn off the script flags. */ F_CLR(sp, SC_SCRIPT); sscr_check(sp); /* Close down the parent's file descriptors. */ if (sc->sh_master != -1) (void)close(sc->sh_master); if (sc->sh_slave != -1) (void)close(sc->sh_slave); /* This should have killed the child. */ (void)proc_wait(sp, sc->sh_pid, "script-shell", 0, 0); /* Free memory. */ free(sc->sh_prompt); free(sc); sp->script = NULL; return (0); } /* * sscr_check -- * Set/clear the global scripting bit. */ static void sscr_check(SCR *sp) { GS *gp; gp = sp->gp; TAILQ_FOREACH(sp, &gp->dq, q) if (F_ISSET(sp, SC_SCRIPT)) { F_SET(gp, G_SCRWIN); return; } F_CLR(gp, G_SCRWIN); } #ifdef HAVE_SYS5_PTY static int sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp) { int master, slave; /* open master terminal */ if ((master = ptym_open(name)) < 0) { errno = ENOENT; /* out of ptys */ return (-1); } /* open slave terminal */ if ((slave = ptys_open(master, name)) >= 0) { *amaster = master; *aslave = slave; } else { errno = ENOENT; /* out of ptys */ return (-1); } if (termp) (void) tcsetattr(slave, TCSAFLUSH, termp); # ifdef TIOCSWINSZ if (winp != NULL) (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp); # endif /* ifdef TIOCSWINSZ */ return (0); } /* * ptym_open -- * This function opens a master pty and returns the file descriptor * to it. pts_name is also returned which is the name of the slave. */ static int ptym_open(char *pts_name) { int fdm; char *ptr, *ptsname(int fdm); openbsd_strlcpy(pts_name, _PATH_SYSV_PTY, strlen(_PATH_SYSV_PTY)); if ((fdm = open(pts_name, O_RDWR)) < 0 ) return (-1); if (grantpt(fdm) < 0) { close(fdm); return (-2); } if (unlockpt(fdm) < 0) { close(fdm); return (-3); } if (unlockpt(fdm) < 0) { close(fdm); return (-3); } /* get slave's name */ if ((ptr = ptsname(fdm)) == NULL) { close(fdm); return (-3); } openbsd_strlcpy(pts_name, ptr, strlen(_PATH_SYSV_PTY)); return (fdm); } /* * ptys_open -- * This function opens the slave pty. */ static int ptys_open(int fdm, char *pts_name) { int fds; if ((fds = open(pts_name, O_RDWR)) < 0) { close(fdm); return (-5); } if (ioctl(fds, I_PUSH, "ptem") < 0) { close(fds); close(fdm); return (-6); } if (ioctl(fds, I_PUSH, "ldterm") < 0) { close(fds); close(fdm); return (-7); } if (ioctl(fds, I_PUSH, "ttcompat") < 0) { close(fds); close(fdm); return (-8); } return (fds); } #endif /* HAVE_SYS5_PTY */ ================================================ FILE: ex/ex_set.c ================================================ /* $OpenBSD: ex_set.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_set -- :set * Ex set option. * * PUBLIC: int ex_set(SCR *, EXCMD *); */ int ex_set(SCR *sp, EXCMD *cmdp) { switch(cmdp->argc) { case 0: opts_dump(sp, CHANGED_DISPLAY); break; default: if (opts_set(sp, cmdp->argv, cmdp->cmd->usage)) return (1); break; } return (0); } ================================================ FILE: ex/ex_shell.c ================================================ /* $OpenBSD: ex_shell.c,v 1.15 2015/03/28 12:54:37 bcallah Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #ifdef __solaris__ # define _XPG7 #endif /* ifdef __solaris__ */ #include #include #include #include #include #include "../common/common.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) /* * ex_shell -- :sh[ell] * Invoke the program named in the SHELL environment variable * with the argument -i. * * PUBLIC: int ex_shell(SCR *, EXCMD *); */ int ex_shell(SCR *sp, EXCMD *cmdp) { int rval; char buf[PATH_MAX]; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* * XXX * Assumes all shells use -i. */ (void)snprintf(buf, sizeof(buf), "%s -i", O_STR(sp, O_SHELL)); /* Restore the window name. */ (void)sp->gp->scr_rename(sp, NULL, 0); /* If we're still in a vi screen, move out explicitly. */ rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE)); /* Set the window name. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* * !!! * Historically, vi didn't require a continue message after the * return of the shell. Match it. */ F_SET(sp, SC_EX_WAIT_NO); return (rval); } /* * ex_exec_proc -- * Run a separate process. * * PUBLIC: int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); */ int ex_exec_proc(SCR *sp, EXCMD *cmdp, char *cmd, const char *msg, int need_newline) { GS *gp; const char *name; pid_t pid; gp = sp->gp; /* We'll need a shell. */ if (opts_empty(sp, O_SHELL, 0)) return (1); /* Enter ex mode. */ if (F_ISSET(sp, SC_VI)) { if (gp->scr_screen(sp, SC_EX)) { ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON); return (1); } (void)gp->scr_attr(sp, SA_ALTERNATE, 0); F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); } /* Put out additional newline, message. */ if (need_newline) (void)ex_puts(sp, "\n"); if (msg != NULL) { (void)ex_puts(sp, msg); (void)ex_puts(sp, "\n"); } (void)ex_fflush(sp); switch (pid = fork()) { case -1: /* Error. */ msgq(sp, M_SYSERR, "fork"); return (1); case 0: /* Utility. */ if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) name = O_STR(sp, O_SHELL); else ++name; execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL); msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); _exit(127); /* NOTREACHED */ default: /* Parent. */ return (proc_wait(sp, pid, cmd, 0, 0)); } /* NOTREACHED */ } /* * proc_wait -- * Wait for one of the processes. * * !!! * The pid_t type varies in size from a short to a long depending on the * system. It has to be cast into something or the standard promotion * rules get you. I'm using a long based on the belief that nobody is * going to make it unsigned and it's unlikely to be a quad. * * PUBLIC: int proc_wait(SCR *, pid_t, const char *, int, int); */ int proc_wait(SCR *sp, pid_t pid, const char *cmd, int silent, int okpipe) { size_t len; int nf, pstat; char *p; /* Wait for the utility, ignoring interruptions. */ for (;;) { errno = 0; if (waitpid(pid, &pstat, 0) != -1) break; if (errno != EINTR) { msgq(sp, M_SYSERR, "waitpid"); return (1); } } /* * Display the utility's exit status. Ignore SIGPIPE from the * parent-writer, as that only means that the utility chose to * exit before reading all of its input. */ if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) { for (; isblank(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: received signal: %s%s", MINIMUM(len, 20), p, len > 20 ? " ..." : "", strsignal(WTERMSIG(pstat)), #ifdef WCOREDUMP WCOREDUMP(pstat) ? "; core dumped" : ""); #else ""); #endif /* ifdef WCOREDUMP */ if (nf) FREE_SPACE(sp, p, 0); return (1); } if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) { /* * Remain silent for "normal" errors when doing shell file * name expansions, they almost certainly indicate nothing * more than a failure to match. * * Remain silent for vi read filter errors. It's historic * practice. */ if (!silent) { for (; isblank(*cmd); ++cmd); p = msg_print(sp, cmd, &nf); len = strlen(p); msgq(sp, M_ERR, "%.*s%s: exited with status %d", MINIMUM(len, 20), p, len > 20 ? " ..." : "", WEXITSTATUS(pstat)); if (nf) FREE_SPACE(sp, p, 0); } return (1); } return (0); } ================================================ FILE: ex/ex_shift.c ================================================ /* $OpenBSD: ex_shift.c,v 1.11 2025/07/30 22:19:13 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" enum which {RETAB, LEFT, RIGHT}; static int shift(SCR *, EXCMD *, enum which); /* * ex_shiftl -- :<[<...] * * * PUBLIC: int ex_shiftl(SCR *, EXCMD *); */ int ex_shiftl(SCR *sp, EXCMD *cmdp) { return (shift(sp, cmdp, LEFT)); } /* * ex_shiftr -- :>[>...] * * PUBLIC: int ex_shiftr(SCR *, EXCMD *); */ int ex_shiftr(SCR *sp, EXCMD *cmdp) { return (shift(sp, cmdp, RIGHT)); } /* * ex_retab -- * Re-expand tabs for expandtab * * PUBLIC: int ex_retab(SCR *sp, EXCMD *cmdp); */ int ex_retab(SCR *sp, EXCMD *cmdp) { return (shift(sp, cmdp, RETAB)); } /* * shift -- * Ex shift support. */ static int shift(SCR *sp, EXCMD *cmdp, enum which rl) { recno_t from, to; size_t blen, len, newcol, newidx, oldcol, oldidx, sw; int curset; char *p, *bp, *tbp; NEEDFILE(sp, cmdp); if (O_VAL(sp, O_SHIFTWIDTH) == 0) { msgq(sp, M_INFO, "shiftwidth option set to 0"); return (0); } /* * When not doing re-expand tabs, copy the lines being shifted into * the unnamed buffer. */ if (rl != RETAB && cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) return (1); /* * The historic version of vi permitted the user to string any number * of '>' or '<' characters together, resulting in an indent of the * appropriate levels. There's a special hack in ex_cmd() so that * cmdp->argv[0] points to the string of '>' or '<' characters. * * Q: What's the difference between the people adding features * to vi and the Girl Scouts? * A: The Girl Scouts have mint cookies and adult supervision. */ for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p) sw += O_VAL(sp, O_SHIFTWIDTH); GET_SPACE_RET(sp, bp, blen, 256); curset = 0; for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { if (db_get(sp, from, DBG_FATAL, &p, &len)) goto err; if (!len) { if (sp->lno == from) curset = 1; continue; } /* * Calculate the old indent amount and the number of * characters it used. */ for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx) if (p[oldidx] == ' ') ++oldcol; else if (p[oldidx] == '\t') oldcol += O_VAL(sp, O_TABSTOP) - oldcol % O_VAL(sp, O_TABSTOP); else break; /* Calculate the new indent amount. */ if (rl == RETAB) newcol = oldcol; else if (rl == RIGHT) newcol = oldcol + sw; else { newcol = oldcol < sw ? 0 : oldcol - sw; if (newcol == oldcol) { if (sp->lno == from) curset = 1; continue; } } /* Get a buffer that will hold the new line. */ ADD_SPACE_RET(sp, bp, blen, newcol + len); /* * Build a new indent string and count the number of * characters it uses. */ tbp = bp; newidx = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { *tbp++ = '\t'; newcol -= O_VAL(sp, O_TABSTOP); } } for (; newcol > 0; --newcol, ++newidx) *tbp++ = ' '; /* Add the original line. */ memcpy(tbp, p + oldidx, len - oldidx); /* Set the replacement line. */ if (db_set(sp, from, bp, (tbp + (len - oldidx)) - bp)) { err: FREE_SPACE(sp, bp, blen); return (1); } /* * !!! * The shift command in historic vi had the usual bizarre * collection of cursor semantics. If called from vi, the * cursor was repositioned to the first non-blank character * of the lowest numbered line shifted. If called from ex, * the cursor was repositioned to the first non-blank of the * highest numbered line shifted. Here, if the cursor isn't * part of the set of lines that are moved, move it to the * first non-blank of the last line shifted. (This makes * ":3>>" in vi work reasonably.) If the cursor is part of * the shifted lines, it doesn't get moved at all. This * permits shifting of marked areas, i.e. ">'a." shifts the * marked area twice, something that couldn't be done with * historic vi. */ if (sp->lno == from) { curset = 1; if (newidx > oldidx) sp->cno += newidx - oldidx; else if (sp->cno >= oldidx - newidx) sp->cno -= oldidx - newidx; } } if (!curset) { sp->lno = to; sp->cno = 0; (void)nonblank(sp, to, &sp->cno); } FREE_SPACE(sp, bp, blen); sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1; return (0); } ================================================ FILE: ex/ex_source.c ================================================ /* $OpenBSD: ex_source.c,v 1.11 2021/10/24 21:24:17 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #undef open /* * ex_sourcefd -- :source already opened file * Execute ex commands from the given file descriptor * * PUBLIC: int ex_sourcefd(SCR *, EXCMD *, int); */ int ex_sourcefd(SCR *sp, EXCMD *cmdp, int fd) { struct stat sb; int len; char *bp, *name; name = cmdp->argv[0]->bp; if (fstat(fd, &sb)) goto err; /* * XXX * I'd like to test to see if the file is too large to malloc. Since * we don't know what size or type off_t's or size_t's are, what the * largest unsigned integral type is, or what random insanity the local * C compiler will perpetrate, doing the comparison in a portable way * is flatly impossible. So, put an fairly unreasonable limit on it, * I don't want to be dropping core here. */ #define MEGABYTE 1048576 if (sb.st_size > (8 * MEGABYTE)) { errno = ENOMEM; goto err; } MALLOC(sp, bp, (size_t)sb.st_size + 1); if (bp == NULL) { (void)close(fd); return (1); } bp[sb.st_size] = '\0'; /* Read the file into memory. */ len = read(fd, bp, (int)sb.st_size); (void)close(fd); if (len == -1 || len != sb.st_size) { if (len != sb.st_size) errno = EIO; free(bp); err: msgq_str(sp, M_SYSERR, name, "%s"); return (1); } /* Put it on the ex queue. */ return (ex_run_str(sp, name, bp, (size_t)sb.st_size, 1, 1)); } /* * ex_source -- :source file * Execute ex commands from a file. * * PUBLIC: int ex_source(SCR *, EXCMD *); */ int ex_source(SCR *sp, EXCMD *cmdp) { char *name; int fd; name = cmdp->argv[0]->bp; if ((fd = open(name, O_RDONLY)) >= 0) return (ex_sourcefd(sp, cmdp, fd)); msgq_str(sp, M_SYSERR, name, "%s"); return (1); } ================================================ FILE: ex/ex_stop.c ================================================ /* $OpenBSD: ex_stop.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_stop -- :stop[!] * :suspend[!] * Suspend execution. * * PUBLIC: int ex_stop(SCR *, EXCMD *); */ int ex_stop(SCR *sp, EXCMD *cmdp) { int allowed; /* For some strange reason, the force flag turns off autowrite. */ if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && file_aw(sp, FS_ALL)) return (1); if (sp->gp->scr_suspend(sp, &allowed)) return (1); if (!allowed) ex_emsg(sp, NULL, EXM_NOSUSPEND); return (0); } ================================================ FILE: ex/ex_subst.c ================================================ /* $OpenBSD: ex_subst.c,v 1.31 2023/06/23 15:06:45 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) #define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ #define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ static int re_conv(SCR *, char **, size_t *, int *); static int re_sub(SCR *, char *, char **, size_t *, size_t *, regmatch_t [10]); static int re_tag_conv(SCR *, char **, size_t *, int *); static int s(SCR *, EXCMD *, char *, regex_t *, unsigned int); /* * ex_s -- * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] * * Substitute on lines matching a pattern. * * PUBLIC: int ex_s(SCR *, EXCMD *); */ int ex_s(SCR *sp, EXCMD *cmdp) { regex_t *re; size_t blen, len; unsigned int flags; int delim; char *bp, *ptrn, *rep, *p, *t; /* * Skip leading white space. * * !!! * Historic vi allowed any non-alphanumeric to serve as the * substitution command delimiter. * * !!! * If the arguments are empty, it's the same as &, i.e. we * repeat the last substitution. */ if (cmdp->argc == 0) goto subagain; for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len > 0; --len, ++p) { if (!isblank(*p)) break; } if (len == 0) subagain: return (ex_subagain(sp, cmdp)); delim = *p++; if (isalnum(delim) || delim == '\\') return (s(sp, cmdp, p, &sp->subre_c, SUB_MUSTSETR)); /* * !!! * The full-blown substitute command reset the remembered * state of the 'c' and 'g' suffices. */ sp->c_suffix = sp->g_suffix = 0; /* * Get the pattern string, toss escaping characters. * * !!! * Historic vi accepted any of the following forms: * * :s/abc/def/ change "abc" to "def" * :s/abc/def change "abc" to "def" * :s/abc/ delete "abc" * :s/abc delete "abc" * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter. * This means that "s/A/\\\\f" replaces "A" with "\\f". It * would be nice to be more regular, i.e. for each layer of * escaping a single escaping character is removed, but that's * not how the historic vi worked. */ for (ptrn = t = p;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; /* * !!! * NULL terminate the pattern string -- it's passed * to regcomp which doesn't understand anything else. */ *t = '\0'; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') *t++ = *p++; } *t++ = *p++; } /* * If the pattern string is empty, use the last RE (not just the * last substitution RE). */ if (*ptrn == '\0') { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } /* Re-compile the RE if necessary. */ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); flags = 0; } else { /* * !!! * Compile the RE. Historic practice is that substitutes set * the search direction as well as both substitute and search * RE's. We compile the RE twice, as we don't want to bother * ref counting the pattern string and (opaque) structure. */ if (re_compile(sp, ptrn, t - ptrn, &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) return (1); if (re_compile(sp, ptrn, t - ptrn, &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) return (1); flags = SUB_FIRST; sp->searchdir = FORWARD; } re = &sp->re_c; /* * Get the replacement string. * * The special character & (\& if O_MAGIC not set) matches the * entire RE. No handling of & is required here, it's done by * re_sub(). * * The special character ~ (\~ if O_MAGIC not set) inserts the * previous replacement string into this replacement string. * Count ~'s to figure out how much space we need. We could * special case nonexistent last patterns or whether or not * O_MAGIC is set, but it's probably not worth the effort. * * QUOTING NOTE: * * Only toss an escaping character if it escapes a delimiter or * if O_MAGIC is set and it escapes a tilde. * * !!! * If the entire replacement pattern is "%", then use the last * replacement pattern. This semantic was added to vi in System * V and then percolated elsewhere, presumably around the time * that it was added to their version of ed(1). */ if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; free(sp->repl); sp->repl = NULL; sp->repl_len = 0; } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) p += p[1] == delim ? 2 : 1; else { for (rep = p, len = 0; p[0] != '\0' && p[0] != delim; ++p, ++len) if (p[0] == '~') len += sp->repl_len; GET_SPACE_RET(sp, bp, blen, len); for (t = bp, len = 0, p = rep;;) { if (p[0] == '\0' || p[0] == delim) { if (p[0] == delim) ++p; break; } if (p[0] == '\\') { if (p[1] == delim) ++p; else if (p[1] == '\\') { *t++ = *p++; ++len; } else if (p[1] == '~') { ++p; if (!O_ISSET(sp, O_MAGIC)) goto tilde; } } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { tilde: ++p; memcpy(t, sp->repl, sp->repl_len); t += sp->repl_len; len += sp->repl_len; continue; } *t++ = *p++; ++len; } if ((sp->repl_len = len) != 0) { free(sp->repl); if ((sp->repl = malloc(len)) == NULL) { msgq(sp, M_SYSERR, NULL); FREE_SPACE(sp, bp, blen); return (1); } memcpy(sp->repl, bp, len); } FREE_SPACE(sp, bp, blen); } return (s(sp, cmdp, p, re, flags)); } /* * ex_subagain -- * [line [,line]] & [cgr] [count] [#lp]] * * Substitute using the last substitute RE and replacement pattern. * * PUBLIC: int ex_subagain(SCR *, EXCMD *); */ int ex_subagain(SCR *sp, EXCMD *cmdp) { if (sp->subre == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp, sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0)); } /* * ex_subtilde -- * [line [,line]] ~ [cgr] [count] [#lp]] * * Substitute using the last RE and last substitute replacement pattern. * * PUBLIC: int ex_subtilde(SCR *, EXCMD *); */ int ex_subtilde(SCR *sp, EXCMD *cmdp) { if (sp->re == NULL) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) return (1); return (s(sp, cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0)); } /* * s -- * Do the substitution. This stuff is *really* tricky. There are lots of * special cases, and general nastiness. Don't mess with it unless you're * pretty confident. * * The nasty part of the substitution is what happens when the replacement * string contains newlines. It's a bit tricky -- consider the information * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is * to build a set of newline offsets which we use to break the line up later, * when the replacement is done. Don't change it unless you're *damned* * confident. */ #define NEEDNEWLINE(sp) { \ if ((sp)->newl_len == (sp)->newl_cnt) { \ (sp)->newl_len += 25; \ REALLOCARRAY((sp), (sp)->newl, \ (sp)->newl_len, sizeof(size_t)); \ if ((sp)->newl == NULL) { \ (sp)->newl_len = 0; \ return (1); \ } \ } \ } #define BUILD(sp, l, len) { \ if (lbclen + (len) > lblen) { \ lblen += MAXIMUM(lbclen + (len), 256); \ REALLOC((sp), lb, lblen); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ } \ memcpy(lb + lbclen, (l), (len)); \ lbclen += (len); \ } #define NEEDSP(sp, len, pnt) { \ if (lbclen + (len) > lblen) { \ lblen += MAXIMUM(lbclen + (len), 256); \ REALLOC((sp), lb, lblen); \ if (lb == NULL) { \ lbclen = 0; \ return (1); \ } \ (pnt) = lb + lbclen; \ } \ } static int s(SCR *sp, EXCMD *cmdp, char *s, regex_t *re, unsigned int flags) { EVENT ev; MARK from, to; TEXTH tiq; recno_t elno, lno, slno; regmatch_t match[10]; size_t blen, cnt, last, lbclen, lblen, len, llen; size_t offset, saved_offset, scno; int lflag, nflag, pflag, rflag; int didsub, do_eol_match, eflags, nempty, eval; int linechanged, matched, quit, rval; unsigned long ul; char *bp, *lb; NEEDFILE(sp, cmdp); slno = sp->lno; scno = sp->cno; /* * !!! * Historically, the 'g' and 'c' suffices were always toggled as flags, * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was * not set, they were initialized to 0 for all substitute commands. If * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user * specified substitute/replacement patterns (see ex_s()). */ if (!O_ISSET(sp, O_EDCOMPATIBLE)) sp->c_suffix = sp->g_suffix = 0; /* * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but * it only displayed the last change. I'd disallow them, but they are * useful in combination with the [v]global commands. In the current * model the problem is combining them with the 'c' flag -- the screen * would have to flip back and forth between the confirm screen and the * ex print screen, which would be pretty awful. We do display all * changes, though, for what that's worth. * * !!! * Historic vi was fairly strict about the order of "options", the * count, and "flags". I'm somewhat fuzzy on the difference between * options and flags, anyway, so this is a simpler approach, and we * just take it them in whatever order the user gives them. (The ex * usage statement doesn't reflect this.) */ lflag = nflag = pflag = rflag = 0; if (s == NULL) goto noargs; for (lno = OOBLNO; *s != '\0'; ++s) switch (*s) { case ' ': case '\t': continue; case '+': ++cmdp->flagoff; break; case '-': --cmdp->flagoff; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (lno != OOBLNO) goto usage; errno = 0; if ((ul = strtoul(s, &s, 10)) >= UINT_MAX) errno = ERANGE; if (*s == '\0') /* Loop increment correction. */ --s; if (errno == ERANGE) { if (ul >= UINT_MAX) msgq(sp, M_ERR, "Count overflow"); else msgq(sp, M_SYSERR, NULL); return (1); } lno = (recno_t)ul; /* * In historic vi, the count was inclusive from the * second address. */ cmdp->addr1.lno = cmdp->addr2.lno; cmdp->addr2.lno += lno - 1; if (!db_exist(sp, cmdp->addr2.lno) && db_last(sp, &cmdp->addr2.lno)) return (1); break; case '#': nflag = 1; break; case 'c': sp->c_suffix = !sp->c_suffix; /* Ex text structure initialization. */ if (F_ISSET(sp, SC_EX)) { memset(&tiq, 0, sizeof(TEXTH)); TAILQ_INIT(&tiq); } break; case 'g': sp->g_suffix = !sp->g_suffix; break; case 'l': lflag = 1; break; case 'p': pflag = 1; break; case 'r': if (LF_ISSET(SUB_FIRST)) { msgq(sp, M_ERR, "Regular expression specified; r flag meaningless"); return (1); } if (!F_ISSET(sp, SC_RE_SEARCH)) { ex_emsg(sp, NULL, EXM_NOPREVRE); return (1); } rflag = 1; re = &sp->re_c; break; default: goto usage; } if (*s != '\0' || (!rflag && LF_ISSET(SUB_MUSTSETR))) { usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { msgq(sp, M_ERR, "The #, l and p flags may not be combined with the c flag in vi mode"); return (1); } /* * bp: if interactive, line cache * blen: if interactive, line cache length * lb: build buffer pointer. * lbclen: current length of built buffer. * lblen; length of build buffer. */ bp = lb = NULL; blen = lbclen = lblen = 0; /* For each line... */ for (matched = quit = 0, lno = cmdp->addr1.lno, elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { /* Someone's unhappy, time to stop. */ if (INTERRUPTED(sp)) break; /* Get the line. */ if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; /* * Make a local copy if doing confirmation -- when calling * the confirm routine we're likely to lose the cached copy. */ if (sp->c_suffix) { if (bp == NULL) { GET_SPACE_RET(sp, bp, blen, llen); } else ADD_SPACE_RET(sp, bp, blen, llen); memcpy(bp, s, llen); s = bp; } /* Start searching from the beginning. */ offset = 0; len = llen; /* Reset the build buffer offset. */ lbclen = 0; /* Reset empty match test variable. */ nempty = -1; /* * We don't want to have to do a setline if the line didn't * change -- keep track of whether or not this line changed. * If doing confirmations, don't want to keep setting the * line if change is refused -- keep track of substitutions. */ didsub = linechanged = 0; (void)didsub; /* New line, do an EOL match. */ do_eol_match = 1; /* It's not NULL terminated, but we pretend it is. */ eflags = REG_STARTEND; /* The search area is from s + offset to the EOL. */ nextmatch: match[0].rm_so = offset; match[0].rm_eo = llen; /* Get the next match. */ eval = regexec(re, (char *)s, 10, match, eflags); /* * There wasn't a match or if there was an error, deal with * it. If there was a previous match in this line, resolve * the changes into the database. Otherwise, just move on. */ if (eval == REG_NOMATCH) goto endmatch; if (eval != 0) { re_error(sp, eval, re); goto err; } matched = 1; /* Only the first search can match an anchored expression. */ eflags |= REG_NOTBOL; /* * !!! * It's possible to match 0-length strings -- for example, the * command s;a*;X;, when matched against the string "aabb" will * result in "XbXbX", i.e. the matches are "aa", the space * between the b's and the space between the b's and the end of * the string. There is a similar space between the beginning * of the string and the a's. The rule that we use (because vi * historically used it) is that any 0-length match, occurring * immediately after a match, is ignored. Otherwise, the above * example would have resulted in "XXbXbX". Another example is * incorrectly using " *" to replace groups of spaces with one * space. * * If the match is empty and at the same place as the end of the * previous match, ignore the match and move forward. If * there's no more characters in the string, we were * attempting to match after the last character, so quit. */ if (match[0].rm_so == nempty && match[0].rm_eo == nempty) { nempty = -1; if (len == 0) goto endmatch; BUILD(sp, s + offset, 1) ++offset; --len; goto nextmatch; } /* Confirm change. */ if (sp->c_suffix) { /* * Set the cursor position for confirmation. Note, * if we matched on a '$', the cursor may be past * the end of line. */ from.lno = to.lno = lno; from.cno = match[0].rm_so; to.cno = match[0].rm_eo; /* * Both ex and vi have to correct for a change before * the first character in the line. */ if (llen == 0) from.cno = to.cno = 0; if (F_ISSET(sp, SC_VI)) { /* * Only vi has to correct for a change after * the last character in the line. * * XXX * It would be nice to change the vi code so * that we could display a cursor past EOL. */ if (to.cno >= llen) to.cno = llen - 1; if (from.cno >= llen) from.cno = llen - 1; sp->lno = from.lno; sp->cno = from.cno; if (vs_refresh(sp, 1)) goto err; vs_update(sp, "Confirm change? [n]", NULL); if (v_event_get(sp, &ev, 0, 0)) goto err; switch (ev.e_event) { case E_CHARACTER: break; case E_EOF: case E_ERR: case E_INTERRUPT: goto lquit; default: v_event_err(sp, &ev); goto lquit; } } else { const int flags = O_ISSET(sp, O_NUMBER) ? E_C_HASH : 0; if (ex_print(sp, cmdp, &from, &to, flags) || ex_scprint(sp, &from, &to)) goto lquit; if (ex_txt(sp, &tiq, 0, TXT_CR)) goto err; ev.e_c = TAILQ_FIRST(&tiq)->lb[0]; } switch (ev.e_c) { case CH_YES: break; default: case CH_NO: didsub = 0; BUILD(sp, s + offset, match[0].rm_eo - offset); goto skip; case CH_QUIT: /* Set the quit/interrupted flags. */ lquit: quit = 1; F_SET(sp->gp, G_INTERRUPTED); /* * Resolve any changes, then return to (and * exit from) the main loop. */ goto endmatch; } } /* * Set the cursor to the last position changed, converting * from 1-based to 0-based. */ sp->lno = lno; sp->cno = match[0].rm_so; /* Copy the bytes before the match into the build buffer. */ BUILD(sp, s + offset, match[0].rm_so - offset); /* Substitute the matching bytes. */ didsub = 1; if (re_sub(sp, s, &lb, &lbclen, &lblen, match)) goto err; /* Set the change flag so we know this line was modified. */ linechanged = 1; /* Move past the matched bytes. */ skip: offset = match[0].rm_eo; len = llen - match[0].rm_eo; /* A match cannot be followed by an empty pattern. */ nempty = match[0].rm_eo; /* * If doing a global change with confirmation, we have to * update the screen. The basic idea is to store the line * so the screen update routines can find it, and restart. */ if (didsub && sp->c_suffix && sp->g_suffix) { /* * The new search offset will be the end of the * modified line. */ saved_offset = lbclen; /* Copy the rest of the line. */ if (len) BUILD(sp, s + offset, len) /* Set the new offset. */ offset = saved_offset; /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; offset -= last; sp->newl_cnt = 0; } /* Store and retrieve the line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; if (db_get(sp, lno, DBG_FATAL, &s, &llen)) goto err; ADD_SPACE_RET(sp, bp, blen, llen) memcpy(bp, s, llen); s = bp; len = llen - offset; /* Restart the build. */ lbclen = 0; BUILD(sp, s, offset); /* * If we haven't already done the after-the-string * match, do one. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (!do_eol_match) goto endmatch; if (offset == len) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } /* * If it's a global: * * If at the end of the string, do a test for the after * the string match. Set REG_NOTEOL so the '$' pattern * only matches once. */ if (sp->g_suffix && do_eol_match) { if (len == 0) { do_eol_match = 0; eflags |= REG_NOTEOL; } goto nextmatch; } endmatch: if (!linechanged) continue; /* Copy any remaining bytes into the build buffer. */ if (len) BUILD(sp, s + offset, len) /* Store inserted lines, adjusting the build buffer. */ last = 0; if (sp->newl_cnt) { for (cnt = 0; cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { if (db_insert(sp, lno, lb + last, sp->newl[cnt] - last)) goto err; last = sp->newl[cnt] + 1; ++sp->rptlines[L_ADDED]; } lbclen -= last; sp->newl_cnt = 0; } /* Store the changed line. */ if (db_set(sp, lno, lb + last, lbclen)) goto err; /* Update changed line counter. */ if (sp->rptlchange != lno) { sp->rptlchange = lno; ++sp->rptlines[L_CHANGED]; } /* * !!! * Display as necessary. Historic practice is to only * display the last line of a line split into multiple * lines. */ if (lflag || nflag || pflag) { from.lno = to.lno = lno; from.cno = to.cno = 0; if (lflag) (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); if (nflag) (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); if (pflag) (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); } } /* * !!! * Historically, vi attempted to leave the cursor at the same place if * the substitution was done at the current cursor position. Otherwise * it moved it to the first non-blank of the last line changed. There * were some problems: for example, :s/$/foo/ with the cursor on the * last character of the line left the cursor on the last character, or * the & command with multiple occurrences of the matching string in the * line usually left the cursor in a fairly random position. * * We try to do the same thing, with the exception that if the user is * doing substitution with confirmation, we move to the last line about * which the user was consulted, as opposed to the last line that they * actually changed. This prevents a screen flash if the user doesn't * change many of the possible lines. */ if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * If not in a global command, and nothing matched, say so. * Else, if none of the lines displayed, put something up. */ rval = 0; if (!matched) { if (!F_ISSET(sp, SC_EX_GLOBAL)) { msgq(sp, M_ERR, "No match found"); goto err; } } else if (!lflag && !nflag && !pflag) F_SET(cmdp, E_AUTOPRINT); if (0) { err: rval = 1; } if (bp != NULL) FREE_SPACE(sp, bp, blen); free(lb); return (rval); } /* * re_compile -- * Compile the RE. * * PUBLIC: int re_compile(SCR *, * PUBLIC: char *, size_t, char **, size_t *, regex_t *, unsigned int); */ int re_compile(SCR *sp, char *ptrn, size_t plen, char **ptrnp, size_t *lenp, regex_t *rep, unsigned int flags) { size_t len; int reflags, replaced, rval; char *p; /* Set RE flags. */ reflags = 0; if (!LF_ISSET(RE_C_TAG)) { if (O_ISSET(sp, O_EXTENDED)) reflags |= REG_EXTENDED; if (O_ISSET(sp, O_IGNORECASE)) reflags |= REG_ICASE; if (O_ISSET(sp, O_ICLOWER)) { for (p = ptrn, len = plen; len > 0; ++p, --len) if (isupper(*p)) break; if (len == 0) reflags |= REG_ICASE; } } /* If we're replacing a saved value, clear the old one. */ if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) { regfree(&sp->re_c); F_CLR(sp, SC_RE_SEARCH); } if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) { regfree(&sp->subre_c); F_CLR(sp, SC_RE_SUBST); } /* * If we're saving the string, it's a pattern we haven't seen before, * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for * later recompilation. Free any previously saved value. */ if (ptrnp != NULL) { if (LF_ISSET(RE_C_TAG)) { if (re_tag_conv(sp, &ptrn, &plen, &replaced)) return (1); } else if (re_conv(sp, &ptrn, &plen, &replaced)) return (1); /* Discard previous pattern. */ free(*ptrnp); *ptrnp = NULL; if (lenp != NULL) *lenp = plen; /* * Copy the string into allocated memory. * * XXX * Regcomp isn't 8-bit clean, so the pattern is NULL-terminated * for now. There's just no other solution. */ MALLOC(sp, *ptrnp, plen + 1); if (*ptrnp != NULL) { memcpy(*ptrnp, ptrn, plen); (*ptrnp)[plen] = '\0'; } /* Free up conversion-routine-allocated memory. */ if (replaced) FREE_SPACE(sp, ptrn, 0); if (*ptrnp == NULL) return (1); ptrn = *ptrnp; } /* * XXX * Regcomp isn't 8-bit clean, so we just lost if the pattern * contained a NULL. Bummer! */ if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) { if (!LF_ISSET(RE_C_SILENT)) re_error(sp, rval, rep); return (1); } if (LF_ISSET(RE_C_SEARCH)) F_SET(sp, SC_RE_SEARCH); if (LF_ISSET(RE_C_SUBST)) F_SET(sp, SC_RE_SUBST); return (0); } /* * re_conv -- * Convert vi's regular expressions into something that the * the POSIX 1003.2 RE functions can handle. * * There are two conversions we make to make vi's RE's (specifically * the global, search, and substitute patterns) work with POSIX RE's. * We assume that \ does "word" searches, which is non-standard * but supported by most regexp libraries.. * * 1: If O_MAGIC is not set, strip backslashes from the magic character * set (.[*~) that have them, and add them to the ones that don't. * 2: If O_MAGIC is not set, the string "\~" is replaced with the text * from the last substitute command's replacement string. If O_MAGIC * is set, it's the string "~". * * !!!/XXX * This doesn't exactly match the historic behavior of vi because we do * the ~ substitution before calling the RE engine, so magic characters * in the replacement string will be expanded by the RE engine, and they * weren't historically. It's a bug. */ static int re_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len, needlen; int magic; char *bp, *p, *t; /* * First pass through, we figure out how much space we'll need. * We do it in two passes, on the grounds that most of the time * the user is doing a search and won't have magic characters. * That way we can skip most of the memory allocation and copies. */ magic = 0; for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '~': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 1; } break; default: needlen += 2; } } else needlen += 1; break; case '~': if (O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += sp->repl_len; } break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) { magic = 1; needlen += 2; } break; default: needlen += 1; break; } if (!magic) { *replacedp = 0; return (0); } /* Get enough memory to hold the final pattern. */ *replacedp = 1; GET_SPACE_RET(sp, bp, blen, needlen); for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len) switch (*p) { case '\\': if (len > 1) { --len; switch (*++p) { case '~': if (O_ISSET(sp, O_MAGIC)) *t++ = '~'; else { memcpy(t, sp->repl, sp->repl_len); t += sp->repl_len; } break; case '.': case '[': case '*': if (O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = '\\'; *t++ = *p; } } else *t++ = '\\'; break; case '~': if (O_ISSET(sp, O_MAGIC)) { memcpy(t, sp->repl, sp->repl_len); t += sp->repl_len; } else *t++ = '~'; break; case '.': case '[': case '*': if (!O_ISSET(sp, O_MAGIC)) *t++ = '\\'; *t++ = *p; break; default: *t++ = *p; break; } *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_tag_conv -- * Convert a tags search path into something that the POSIX * 1003.2 RE functions can handle. */ static int re_tag_conv(SCR *sp, char **ptrnp, size_t *plenp, int *replacedp) { size_t blen, len; int lastdollar; char *bp, *p, *t; len = *plenp; /* Max memory usage is 2 times the length of the string. */ *replacedp = 1; GET_SPACE_RET(sp, bp, blen, len * 2); p = *ptrnp; t = bp; /* If the last character is a '/' or '?', we just strip it. */ if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?')) --len; /* If the next-to-last or last character is a '$', it's magic. */ if (len > 0 && p[len - 1] == '$') { --len; lastdollar = 1; } else lastdollar = 0; /* If the first character is a '/' or '?', we just strip it. */ if (len > 0 && (p[0] == '/' || p[0] == '?')) { ++p; --len; } /* If the first or second character is a '^', it's magic. */ if (p[0] == '^') { *t++ = *p++; --len; } /* * Escape every other magic character we can find, meanwhile stripping * the backslashes ctags inserts when escaping the search delimiter * characters. */ for (; len > 0; --len) { if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) { ++p; if (len > 1) { --len; } } else if (strchr("^.[]$*", p[0])) *t++ = '\\'; *t++ = *p++; if (len == 0) break; } if (lastdollar) *t++ = '$'; *ptrnp = bp; *plenp = t - bp; return (0); } /* * re_error -- * Report a regular expression error. * * PUBLIC: void re_error(SCR *, int, regex_t *); */ void re_error(SCR *sp, int errcode, regex_t *preg) { size_t s; char *oe; s = regerror(errcode, preg, "", 0); if ((oe = malloc(s)) == NULL) msgq(sp, M_SYSERR, NULL); else { (void)regerror(errcode, preg, oe, s); msgq(sp, M_ERR, "RE error: %s", oe); free(oe); } } /* * re_sub -- * Do the substitution for a regular expression. */ static int re_sub(SCR *sp, char *ip, char **lbp, size_t *lbclenp, size_t *lblenp, regmatch_t match[10]) { enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv; size_t lbclen, lblen; /* Local copies. */ size_t mlen; /* Match length. */ size_t rpl; /* Remaining replacement length. */ char *rp; /* Replacement pointer. */ int ch; int no; /* Match replacement offset. */ char *p, *t; /* Buffer pointers. */ char *lb; /* Local copies. */ lb = *lbp; /* Get local copies. */ lbclen = *lbclenp; lblen = *lblenp; /* * QUOTING NOTE: * * There are some special sequences that vi provides in the * replacement patterns. * & string the RE matched (\& if nomagic set) * \# n-th regular subexpression * \E end \U, \L conversion * \e end \U, \L conversion * \l convert the next character to lower-case * \L convert to lower-case, until \E, \e, or end of replacement * \u convert the next character to upper-case * \U convert to upper-case, until \E, \e, or end of replacement * * Otherwise, since this is the lowest level of replacement, discard * all escaping characters. This (hopefully) matches historic practice. */ #define OUTCH(ch, nltrans) { \ CHAR_T __ch = (ch); \ unsigned int __value = KEY_VAL(sp, __ch); \ if ((nltrans) && (__value == K_CR || __value == K_NL)) { \ NEEDNEWLINE(sp); \ sp->newl[sp->newl_cnt++] = lbclen; \ } else if (conv != C_NOTSET) { \ switch (conv) { \ case C_ONELOWER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_LOWER: \ if (isupper(__ch)) \ __ch = tolower(__ch); \ break; \ case C_ONEUPPER: \ conv = C_NOTSET; \ /* FALLTHROUGH */ \ case C_UPPER: \ if (islower(__ch)) \ __ch = toupper(__ch); \ break; \ default: \ abort(); \ } \ } \ NEEDSP(sp, 1, p); \ *p++ = __ch; \ ++lbclen; \ } conv = C_NOTSET; for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) { switch (ch = *rp++) { case '&': if (O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '\\': if (rpl == 0) break; --rpl; switch (ch = *rp) { case '&': ++rp; if (!O_ISSET(sp, O_MAGIC)) { no = 0; goto subzero; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': no = *rp++ - '0'; subzero: if (match[no].rm_so == -1 || match[no].rm_eo == -1) break; mlen = match[no].rm_eo - match[no].rm_so; for (t = ip + match[no].rm_so; mlen--; ++t) OUTCH(*t, 0); continue; case 'e': case 'E': ++rp; conv = C_NOTSET; continue; case 'l': ++rp; conv = C_ONELOWER; continue; case 'L': ++rp; conv = C_LOWER; continue; case 'u': ++rp; conv = C_ONEUPPER; continue; case 'U': ++rp; conv = C_UPPER; continue; default: ++rp; break; } } OUTCH(ch, 1); } *lbp = lb; /* Update caller's information. */ *lbclenp = lbclen; *lblenp = lblen; return (0); } ================================================ FILE: ex/ex_tag.c ================================================ /* $OpenBSD: ex_tag.c,v 1.26 2021/10/24 21:24:17 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * This code is derived from software contributed to Berkeley by * David Hitz of Auspex Systems, Inc. * * See the LICENSE.md file for redistribution information. */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __solaris__ # undef _STRICT_STDC # undef __EXTENSIONS__ # define __EXTENSIONS__ #endif /* ifdef __solaris__ */ #include #include #include "../common/common.h" #include "../vi/vi.h" #include "tag.h" #undef open static char *binary_search(char *, char *, char *); static int compare(char *, char *, char *); static void ctag_file(SCR *, TAGF *, char *, char **, size_t *); static int ctag_search(SCR *, char *, size_t, char *); static int ctag_sfile(SCR *, TAGF *, TAGQ *, char *); static TAGQ *ctag_slist(SCR *, char *); static char *linear_search(char *, char *, char *, long); static int tag_copy(SCR *, TAG *, TAG **); static int tag_pop(SCR *, TAGQ *, int); static int tagf_copy(SCR *, TAGF *, TAGF **); static int tagf_free(SCR *, TAGF *); static int tagq_copy(SCR *, TAGQ *, TAGQ **); /* * ex_tag_first -- * The tag code can be entered from main, e.g., "vi -t tag". * * PUBLIC: int ex_tag_first(SCR *, char *); */ int ex_tag_first(SCR *sp, char *tagarg) { ARGS *ap[2], a; EXCMD cmd; /* Build an argument for the ex :tag command. */ ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap); ex_cadd(&cmd, &a, tagarg, strlen(tagarg)); /* * XXX * Historic vi went ahead and created a temporary file when it failed * to find the tag. We match historic practice, but don't distinguish * between real error and failure to find the tag. */ if (ex_tag_push(sp, &cmd)) return (0); /* Display tags in the center of the screen. */ F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_CENTER); return (0); } /* * ex_tag_push -- ^] * :tag[!] [string] * * Enter a new TAGQ context based on a ctag string. * * PUBLIC: int ex_tag_push(SCR *, EXCMD *); */ int ex_tag_push(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; FREF *frp; TAG *rtp; TAGQ *rtqp, *tqp; recno_t lno; size_t cno; long tl; int force, istmp; exp = EXP(sp); switch (cmdp->argc) { case 1: free(exp->tag_last); if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Taglength may limit the number of characters. */ if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl) exp->tag_last[tl] = '\0'; break; case 0: if (exp->tag_last == NULL) { msgq(sp, M_ERR, "No previous tag entered"); return (1); } break; default: abort(); } /* Get the tag information. */ if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL) return (1); /* * Allocate all necessary memory before swapping screens. Initialize * flags so we know what to free. */ rtp = NULL; rtqp = NULL; if (TAILQ_EMPTY(&exp->tq)) { /* Initialize the `local context' tag queue structure. */ CALLOC_GOTO(sp, rtqp, 1, sizeof(TAGQ)); TAILQ_INIT(&rtqp->tagq); /* Initialize and link in its tag structure. */ CALLOC_GOTO(sp, rtp, 1, sizeof(TAG)); TAILQ_INSERT_HEAD(&rtqp->tagq, rtp, q); rtqp->current = rtp; } /* * Stick the current context information in a convenient place, we're * about to lose it. Note, if we're called on editor startup, there * will be no FREF structure. */ frp = sp->frp; lno = sp->lno; cno = sp->cno; istmp = frp == NULL || (F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN)); /* Try to switch to the tag. */ force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (F_ISSET(cmdp, E_NEWSCREEN)) { if (ex_tag_Nswitch(sp, TAILQ_FIRST(&tqp->tagq), force)) goto err; /* Everything else gets done in the new screen. */ sp = sp->nextdisp; exp = EXP(sp); } else if (ex_tag_nswitch(sp, TAILQ_FIRST(&tqp->tagq), force)) goto err; /* * If this is the first tag, put a `current location' queue entry * in place, so we can pop all the way back to the current mark. * Note, it doesn't point to much of anything, it's a placeholder. */ if (TAILQ_EMPTY(&exp->tq)) { TAILQ_INSERT_HEAD(&exp->tq, rtqp, q); } else rtqp = TAILQ_FIRST(&exp->tq); /* Link the new TAGQ structure into place. */ TAILQ_INSERT_HEAD(&exp->tq, tqp, q); (void)ctag_search(sp, tqp->current->search, tqp->current->slen, tqp->tag); /* * Move the current context from the temporary save area into the * right structure. * * If we were in a temporary file, we don't have a context to which * we can return, so just make it be the same as what we're moving * to. It will be a little odd that ^T doesn't change anything, but * I don't think it's a big deal. */ if (istmp) { rtqp->current->frp = sp->frp; rtqp->current->lno = sp->lno; rtqp->current->cno = sp->cno; } else { rtqp->current->frp = frp; rtqp->current->lno = lno; rtqp->current->cno = cno; } return (0); err: alloc_err: free(rtqp); free(rtp); tagq_free(sp, tqp); return (1); } /* * ex_tag_next -- * Switch context to the next TAG. * * PUBLIC: int ex_tag_next(SCR *, EXCMD *); */ int ex_tag_next(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; exp = EXP(sp); if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } if ((tp = TAILQ_NEXT(tqp->current, q)) == NULL) { msgq(sp, M_ERR, "Already at the last tag of this group"); return (1); } if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); tqp->current = tp; (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); return (0); } /* * ex_tag_prev -- * Switch context to the next TAG. * * PUBLIC: int ex_tag_prev(SCR *, EXCMD *); */ int ex_tag_prev(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; exp = EXP(sp); if ((tqp = TAILQ_FIRST(&exp->tq)) == NULL) { tag_msg(sp, TAG_EMPTY, NULL); return (0); } if ((tp = TAILQ_PREV(tqp->current, _tagqh, q)) == NULL) { msgq(sp, M_ERR, "Already at the first tag of this group"); return (1); } if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) return (1); tqp->current = tp; (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); return (0); } /* * ex_tag_nswitch -- * Switch context to the specified TAG. * * PUBLIC: int ex_tag_nswitch(SCR *, TAG *, int); */ int ex_tag_nswitch(SCR *sp, TAG *tp, int force) { /* Get a file structure. */ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) return (1); /* If not changing files, return, we're done. */ if (tp->frp == sp->frp) return (0); /* Check for permission to leave. */ if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) return (1); /* Initialize the new file. */ if (file_init(sp, tp->frp, NULL, FS_SETALT)) return (1); /* Display tags in the center of the screen. */ F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_CENTER); /* Switch. */ F_SET(sp, SC_FSWITCH); return (0); } /* * ex_tag_Nswitch -- * Switch context to the specified TAG in a new screen. * * PUBLIC: int ex_tag_Nswitch(SCR *, TAG *, int); */ int ex_tag_Nswitch(SCR *sp, TAG *tp, int force) { SCR *new; /* Get a file structure. */ if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) return (1); /* Get a new screen. */ if (screen_init(sp->gp, sp, &new)) return (1); if (vs_split(sp, new, 0)) { (void)file_end(new, new->ep, 1); (void)screen_end(new); return (1); } /* Get a backing file. */ if (tp->frp == sp->frp) { /* Copy file state. */ new->ep = sp->ep; ++new->ep->refcnt; new->frp = tp->frp; new->frp->flags = sp->frp->flags; } else if (file_init(new, tp->frp, NULL, force)) { (void)vs_discard(new, NULL); (void)screen_end(new); return (1); } /* Create the argument list. */ new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name); /* Display tags in the center of the screen. */ F_CLR(new, SC_SCR_TOP); F_SET(new, SC_SCR_CENTER); /* Switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * ex_tag_pop -- ^T * :tagp[op][!] [number | file] * * Pop to a previous TAGQ context. * * PUBLIC: int ex_tag_pop(SCR *, EXCMD *); */ int ex_tag_pop(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; TAGQ *tqp, *dtqp = NULL; size_t arglen; long off; char *arg, *p, *t; /* Check for an empty stack. */ exp = EXP(sp); if (TAILQ_EMPTY(&exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } /* Find the last TAG structure that we're going to DISCARD! */ switch (cmdp->argc) { case 0: /* Pop one tag. */ dtqp = TAILQ_FIRST(&exp->tq); break; case 1: /* Name or number. */ arg = cmdp->argv[0]->bp; off = strtol(arg, &p, 10); if (*p != '\0') goto filearg; /* Number: pop that many queue entries. */ if (off < 1) return (0); TAILQ_FOREACH(tqp, &exp->tq, q) { if (--off <= 1) break; } if (tqp == NULL) { msgq(sp, M_ERR, "Less than %s entries on the tags stack; use :display t[ags]", arg); return (1); } dtqp = tqp; break; /* File argument: pop to that queue entry. */ filearg: arglen = strlen(arg); for (tqp = TAILQ_FIRST(&exp->tq); tqp; dtqp = tqp, tqp = TAILQ_NEXT(tqp, q)) { /* Don't pop to the current file. */ if (tqp == TAILQ_FIRST(&exp->tq)) continue; p = tqp->current->frp->name; if ((t = strrchr(p, '/')) == NULL) t = p; else ++t; if (!strncmp(arg, t, arglen)) break; } if (tqp == NULL) { msgq_str(sp, M_ERR, arg, "No file %s on the tags stack to return to; use :display t[ags]"); return (1); } if (tqp == TAILQ_FIRST(&exp->tq)) return (0); break; default: abort(); /* NOTREACHED */ } return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE))); } /* * ex_tag_top -- :tagt[op][!] * Clear the tag stack. * * PUBLIC: int ex_tag_top(SCR *, EXCMD *); */ int ex_tag_top(SCR *sp, EXCMD *cmdp) { EX_PRIVATE *exp; exp = EXP(sp); /* Check for an empty stack. */ if (TAILQ_EMPTY(&exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (1); } /* Return to the oldest information. */ return (tag_pop(sp, TAILQ_PREV(TAILQ_LAST(&exp->tq, _tqh), _tqh, q), FL_ISSET(cmdp->iflags, E_C_FORCE))); } /* * tag_pop -- * Pop up to and including the specified TAGQ context. */ static int tag_pop(SCR *sp, TAGQ *dtqp, int force) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; exp = EXP(sp); /* * Update the cursor from the saved TAG information of the TAG * structure we're moving to. */ tp = TAILQ_NEXT(dtqp, q)->current; if (tp->frp == sp->frp) { sp->lno = tp->lno; sp->cno = tp->cno; } else { if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) return (1); tp->frp->lno = tp->lno; tp->frp->cno = tp->cno; F_SET(sp->frp, FR_CURSORSET); if (file_init(sp, tp->frp, NULL, FS_SETALT)) return (1); F_SET(sp, SC_FSWITCH); } /* Pop entries off the queue up to and including dtqp. */ do { tqp = TAILQ_FIRST(&exp->tq); if (tagq_free(sp, tqp)) return (0); } while (tqp != dtqp); /* * If only a single tag left, we've returned to the first tag point, * and the stack is now empty. */ if (TAILQ_NEXT(TAILQ_FIRST(&exp->tq), q) == NULL) tagq_free(sp, TAILQ_FIRST(&exp->tq)); return (0); } /* * ex_tag_display -- * Display the list of tags. * * PUBLIC: int ex_tag_display(SCR *); */ int ex_tag_display(SCR *sp) { EX_PRIVATE *exp; TAG *tp; TAGQ *tqp; int cnt; size_t len; char *p; exp = EXP(sp); if (TAILQ_EMPTY(&exp->tq)) { tag_msg(sp, TAG_EMPTY, NULL); return (0); } tqp = TAILQ_FIRST(&exp->tq); /* * We give the file name 20 columns and the search string the rest. * If there's not enough room, we don't do anything special, it's * not worth the effort, it just makes the display more confusing. * * We also assume that characters in file names map 1-1 to printing * characters. This might not be true, but I don't think it's worth * fixing. (The obvious fix is to pass the filenames through the * msg_print function.) */ #define L_NAME 30 /* Name. */ #define L_SLOP 4 /* Leading number plus trailing *. */ #define L_SPACE 5 /* Spaces after name, before tag. */ #define L_TAG 20 /* Tag. */ if (sp->cols <= L_NAME + L_SLOP) { msgq(sp, M_ERR, "Display too small."); return (0); } /* * Display the list of tags for each queue entry. The first entry * is numbered, and the current tag entry has an asterisk appended. */ cnt = 0; TAILQ_FOREACH(tqp, &exp->tq, q) { if (INTERRUPTED(sp)) break; ++cnt; TAILQ_FOREACH(tp, &tqp->tagq, q) { if (tp == TAILQ_FIRST(&tqp->tagq)) (void)ex_printf(sp, "%2d ", cnt); else (void)ex_printf(sp, " "); p = tp->frp == NULL ? tp->fname : tp->frp->name; if ((len = strlen(p)) > L_NAME) { len = len - (L_NAME - 4); (void)ex_printf(sp, " ... %*.*s", L_NAME - 4, L_NAME - 4, p + len); } else (void)ex_printf(sp, " %*.*s", L_NAME, L_NAME, p); if (tqp->current == tp) (void)ex_printf(sp, "*"); if (tp == TAILQ_FIRST(&tqp->tagq) && tqp->tag != NULL && (sp->cols - L_NAME) >= L_TAG + L_SPACE) { len = strlen(tqp->tag); if (len > sp->cols - (L_NAME + L_SPACE)) len = sp->cols - (L_NAME + L_SPACE); (void)ex_printf(sp, "%s%.*s", tqp->current == tp ? " " : " ", (int)len, tqp->tag); } (void)ex_printf(sp, "\n"); } } return (0); } /* * ex_tag_copy -- * Copy a screen's tag structures. * * PUBLIC: int ex_tag_copy(SCR *, SCR *); */ int ex_tag_copy(SCR *orig, SCR *sp) { EX_PRIVATE *oexp, *nexp; TAGQ *aqp, *tqp; TAG *ap, *tp; TAGF *atfp, *tfp; oexp = EXP(orig); nexp = EXP(sp); /* Copy tag queue and tags stack. */ TAILQ_FOREACH(aqp, &oexp->tq, q) { if (tagq_copy(sp, aqp, &tqp)) return (1); TAILQ_FOREACH(ap, &aqp->tagq, q) { if (tag_copy(sp, ap, &tp)) return (1); /* Set the current pointer. */ if (aqp->current == ap) tqp->current = tp; TAILQ_INSERT_TAIL(&tqp->tagq, tp, q); } TAILQ_INSERT_TAIL(&nexp->tq, tqp, q); } /* Copy list of tag files. */ TAILQ_FOREACH(atfp, &oexp->tagfq, q) { if (tagf_copy(sp, atfp, &tfp)) return (1); TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q); } /* Copy the last tag. */ if (oexp->tag_last != NULL && (nexp->tag_last = strdup(oexp->tag_last)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } return (0); } /* * tagf_copy -- * Copy a TAGF structure and return it in new memory. */ static int tagf_copy(SCR *sp, TAGF *otfp, TAGF **tfpp) { TAGF *tfp; MALLOC_RET(sp, tfp, sizeof(TAGF)); *tfp = *otfp; /* XXX: Allocate as part of the TAGF structure!!! */ if ((tfp->name = strdup(otfp->name)) == NULL) { free(tfp); return (1); } *tfpp = tfp; return (0); } /* * tagq_copy -- * Copy a TAGQ structure and return it in new memory. */ static int tagq_copy(SCR *sp, TAGQ *otqp, TAGQ **tqpp) { TAGQ *tqp; size_t len; len = sizeof(TAGQ); if (otqp->tag != NULL) len += otqp->tlen + 1; MALLOC_RET(sp, tqp, len); memcpy(tqp, otqp, len); TAILQ_INIT(&tqp->tagq); tqp->current = NULL; if (otqp->tag != NULL) tqp->tag = tqp->buf; *tqpp = tqp; return (0); } /* * tag_copy -- * Copy a TAG structure and return it in new memory. */ static int tag_copy(SCR *sp, TAG *otp, TAG **tpp) { TAG *tp; size_t len; len = sizeof(TAG); if (otp->fname != NULL) len += otp->fnlen + 1; if (otp->search != NULL) len += otp->slen + 1; MALLOC_RET(sp, tp, len); memcpy(tp, otp, len); if (otp->fname != NULL) tp->fname = tp->buf; if (otp->search != NULL) tp->search = tp->fname + otp->fnlen + 1; *tpp = tp; return (0); } /* * tagf_free -- * Free a TAGF structure. */ static int tagf_free(SCR *sp, TAGF *tfp) { EX_PRIVATE *exp; exp = EXP(sp); TAILQ_REMOVE(&exp->tagfq, tfp, q); free(tfp->name); free(tfp); return (0); } /* * tagq_free -- * Free a TAGQ structure (and associated TAG structures). * * PUBLIC: int tagq_free(SCR *, TAGQ *); */ int tagq_free(SCR *sp, TAGQ *tqp) { EX_PRIVATE *exp; TAGQ *ttqp; TAG *tp; exp = EXP(sp); while ((tp = TAILQ_FIRST(&tqp->tagq))) { TAILQ_REMOVE(&tqp->tagq, tp, q); free(tp); } /* * !!! * If allocated and then the user failed to switch files, the TAGQ * structure was never attached to any list. */ TAILQ_FOREACH(ttqp, &exp->tq, q) { if (ttqp == tqp) { TAILQ_REMOVE(&exp->tq, tqp, q); break; } } free(tqp); return (0); } /* * tag_msg * A few common messages. * * PUBLIC: void tag_msg(SCR *, tagmsg_t, char *); */ void tag_msg(SCR *sp, tagmsg_t msg, char *tag) { switch (msg) { case TAG_BADLNO: msgq_str(sp, M_ERR, tag, "%s: the tag's line number is past the end of the file"); break; case TAG_EMPTY: msgq(sp, M_INFO, "The tags stack is empty"); break; case TAG_SEARCH: msgq_str(sp, M_ERR, tag, "%s: search pattern not found"); break; default: abort(); } } /* * ex_tagf_alloc -- * Create a new list of ctag files. * * PUBLIC: int ex_tagf_alloc(SCR *, char *); */ int ex_tagf_alloc(SCR *sp, char *str) { EX_PRIVATE *exp; TAGF *tfp; size_t len; char *p, *t; /* Free current queue. */ exp = EXP(sp); while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL) tagf_free(sp, tfp); /* Create new queue. */ for (p = t = str;; ++p) { if (*p == '\0' || isblank(*p)) { if ((len = p - t) > 1) { MALLOC_RET(sp, tfp, sizeof(TAGF)); MALLOC(sp, tfp->name, len + 1); if (tfp->name == NULL) { free(tfp); return (1); } memcpy(tfp->name, t, len); tfp->name[len] = '\0'; tfp->flags = 0; TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q); } t = p + 1; } if (*p == '\0') break; } return (0); } /* Free previous queue. */ /* * ex_tag_free -- * Free the ex tag information. * * PUBLIC: int ex_tag_free(SCR *); */ int ex_tag_free(SCR *sp) { EX_PRIVATE *exp; TAGF *tfp; TAGQ *tqp; /* Free up tag information. */ exp = EXP(sp); while ((tqp = TAILQ_FIRST(&exp->tq))) tagq_free(sp, tqp); /* tagq_free removes tqp from queue. */ while ((tfp = TAILQ_FIRST(&exp->tagfq)) != NULL) tagf_free(sp, tfp); free(exp->tag_last); return (0); } /* * ctag_search -- * Search a file for a tag. */ static int ctag_search(SCR *sp, char *search, size_t slen, char *tag) { MARK m; char *p; /* * !!! * The historic tags file format (from a long, long time ago...) * used a line number, not a search string. I got complaints, so * people are still using the format. POSIX 1003.2 permits it. */ if (isdigit(search[0])) { m.lno = atoi(search); if (!db_exist(sp, m.lno)) { tag_msg(sp, TAG_BADLNO, tag); return (1); } } else { /* * Search for the tag; cheap fallback for C functions * if the name is the same but the arguments have changed. */ m.lno = 1; m.cno = 0; if (f_search(sp, &m, &m, search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) { if ((p = strrchr(search, '(')) != NULL) { slen = p - search; if (f_search(sp, &m, &m, search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) goto notfound; } else { notfound: tag_msg(sp, TAG_SEARCH, tag); return (1); } } /* * !!! * Historically, tags set the search direction if it wasn't * already set. */ if (sp->searchdir == NOTSET) sp->searchdir = FORWARD; } /* * !!! * Tags move to the first non-blank, NOT the search pattern start. */ sp->lno = m.lno; sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); return (0); } /* * ctag_slist -- * Search the list of tags files for a tag, and return tag queue. */ static TAGQ * ctag_slist(SCR *sp, char *tag) { EX_PRIVATE *exp; TAGF *tfp; TAGQ *tqp; size_t len; int echk; exp = EXP(sp); /* Allocate and initialize the tag queue structure. */ len = strlen(tag); CALLOC_GOTO(sp, tqp, 1, sizeof(TAGQ) + len + 1); TAILQ_INIT(&tqp->tagq); tqp->tag = tqp->buf; memcpy(tqp->tag, tag, (tqp->tlen = len) + 1); /* * Find the tag, only display missing file messages once, and * then only if we didn't find the tag. */ echk = 0; TAILQ_FOREACH(tfp, &exp->tagfq, q) if (ctag_sfile(sp, tfp, tqp, tag)) { echk = 1; F_SET(tfp, TAGF_ERR); } else F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN); /* Check to see if we found anything. */ if (TAILQ_EMPTY(&tqp->tagq)) { msgq_str(sp, M_ERR, tag, "%s: tag not found"); if (echk) TAILQ_FOREACH(tfp, &exp->tagfq, q) if (F_ISSET(tfp, TAGF_ERR) && !F_ISSET(tfp, TAGF_ERR_WARN)) { errno = tfp->errnum; msgq_str(sp, M_SYSERR, tfp->name, "%s"); F_SET(tfp, TAGF_ERR_WARN); } free(tqp); return (NULL); } tqp->current = TAILQ_FIRST(&tqp->tagq); return (tqp); alloc_err: return (NULL); } /* * ctag_sfile -- * Search a tags file for a tag, adding any found to the tag queue. */ static int ctag_sfile(SCR *sp, TAGF *tfp, TAGQ *tqp, char *tname) { struct stat sb; TAG *tp; size_t dlen, nlen, slen; int fd, i, nf1, nf2; char *back, *cname, *dname, *front, *map, *name, *p, *search, *t; long tl; if ((fd = open(tfp->name, O_RDONLY)) < 0) { tfp->errnum = errno; return (1); } /* * XXX * We'd like to test if the file is too big to mmap. Since we don't * know what size or type off_t's or size_t's are, what the largest * unsigned integral type is, or what random insanity the local C * compiler will perpetrate, doing the comparison in a portable way * is flatly impossible. Hope mmap fails if the file is too large. */ if (fstat(fd, &sb) != 0 || (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, (off_t)0)) == MAP_FAILED) { tfp->errnum = errno; (void)close(fd); return (1); } tl = O_VAL(sp, O_TAGLENGTH); front = map; back = front + sb.st_size; front = binary_search(tname, front, back); front = linear_search(tname, front, back, tl); if (front == NULL) goto done; /* * Initialize and link in the tag structure(s). The historic ctags * file format only permitted a single tag location per tag. The * obvious extension to permit multiple tags locations per tag is to * output multiple records in the standard format. Unfortunately, * this won't work correctly with historic ex/vi implementations, * because their binary search assumes that there's only one record * per tag, and so will use a random tag entry if there si more than * one. This code handles either format. * * The tags file is in the following format: * * | * * Figure out how long everything is so we can allocate in one swell * foop, but discard anything that looks wrong. */ for (;;) { /* Nul-terminate the end of the line. */ for (p = front; p < back && *p != '\n'; ++p); if (p == back || *p != '\n') break; *p = '\0'; /* Update the pointers for the next time. */ t = p + 1; p = front; front = t; /* Break the line into tokens. */ for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i) switch (i) { case 0: /* Tag. */ cname = t; break; case 1: /* Filename. */ name = t; nlen = strlen(name); break; } /* Check for corruption. */ if (i != 2 || p == NULL || t == NULL) goto corrupt; /* The rest of the string is the search pattern. */ search = p; if ((slen = strlen(p)) == 0) { corrupt: p = msg_print(sp, tname, &nf1); t = msg_print(sp, tfp->name, &nf2); msgq(sp, M_ERR, "%s: corrupted tag in %s", p, t); if (nf1) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); continue; } /* Check for passing the last entry. */ if (tl ? strncmp(tname, cname, tl) : strcmp(tname, cname)) break; /* Resolve the file name. */ ctag_file(sp, tfp, name, &dname, &dlen); CALLOC_GOTO(sp, tp, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1); tp->fname = tp->buf; if (dlen != 0) { memcpy(tp->fname, dname, dlen); tp->fname[dlen] = '/'; ++dlen; } memcpy(tp->fname + dlen, name, nlen + 1); tp->fnlen = dlen + nlen; tp->search = tp->fname + tp->fnlen + 1; memcpy(tp->search, search, (tp->slen = slen) + 1); TAILQ_INSERT_TAIL(&tqp->tagq, tp, q); } alloc_err: done: if (munmap(map, (size_t)sb.st_size)) msgq(sp, M_SYSERR, "munmap"); if (close(fd)) msgq(sp, M_SYSERR, "close"); return (0); } /* * ctag_file -- * Search for the right path to this file. */ static void ctag_file(SCR *sp, TAGF *tfp, char *name, char **dirp, size_t *dlenp) { struct stat sb; char *p, buf[PATH_MAX]; /* * !!! * If the tag file path is a relative path, see if it exists. If it * doesn't, look relative to the tags file path. It's okay for a tag * file to not exist, and historically, vi simply displayed a "new" * file. However, if the path exists relative to the tag file, it's * pretty clear what's happening, so we may as well get it right. */ *dlenp = 0; if (name[0] != '/' && stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) { *p = '\0'; (void)snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name); if (stat(buf, &sb) == 0) { *dirp = tfp->name; *dlenp = strlen(*dirp); } *p = '/'; } } /* * Binary search for "string" in memory between "front" and "back". * * This routine is expected to return a pointer to the start of a line at * *or before* the first word matching "string". Relaxing the constraint * this way simplifies the algorithm. * * Invariants: * front points to the beginning of a line at or before the first * matching string. * * back points to the beginning of a line at or after the first * matching line. * * Base of the Invariants. * front = NULL; * back = EOF; * * Advancing the Invariants: * * p = first newline after halfway point from front to back. * * If the string at "p" is not greater than the string to match, * p is the new front. Otherwise it is the new back. * * Termination: * * The definition of the routine allows it return at any point, * since front is always at or before the line to print. * * In fact, it returns when the chosen "p" equals "back". This * implies that there exists a string is least half as long as * (back - front), which in turn implies that a linear search will * be no more expensive than the cost of simply printing a string or two. * * Trying to continue with binary search at this point would be * more trouble than it's worth. */ #define EQUAL 0 #define GREATER 1 #define LESS (-1) #define SKIP_PAST_NEWLINE(p, back) while ((p) < (back) && *(p)++ != '\n'); static char * binary_search(char *string, char *front, char *back) { char *p; p = front + (back - front) / 2; SKIP_PAST_NEWLINE(p, back); while (p != back) { if (compare(string, p, back) == GREATER) front = p; else back = p; p = front + (back - front) / 2; SKIP_PAST_NEWLINE(p, back); } return (front); } /* * Find the first line that starts with string, linearly searching from front * to back. * * Return NULL for no such line. * * This routine assumes: * * o front points at the first character in a line. * o front is before or at the first line to be printed. */ static char * linear_search(char *string, char *front, char *back, long tl) { char *end; while (front < back) { end = tl && back-front > tl ? front+tl : back; switch (compare(string, front, end)) { case EQUAL: /* Found it. */ return (front); case LESS: /* No such string. */ return (NULL); case GREATER: /* Keep going. */ break; } SKIP_PAST_NEWLINE(front, back); } return (NULL); } /* * Return LESS, GREATER, or EQUAL depending on how the string1 compares * with string2 (s1 ??? s2). * * o Matches up to len(s1) are EQUAL. * o Matches up to len(s2) are GREATER. * * The string "s1" is NULL terminated. The string s2 is '\t', space, (or * "back") terminated. * * !!! * Reasonably modern ctags programs use tabs as separators, not spaces. * However, historic programs did use spaces, and, I got complaints. */ static int compare(char *s1, char *s2, char *back) { for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2) if (*s1 != *s2) return (*s1 < *s2 ? LESS : GREATER); return (*s1 ? GREATER : s2 < back && (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL); } ================================================ FILE: ex/ex_txt.c ================================================ /* $OpenBSD: ex_txt.c,v 1.17 2020/04/30 10:40:21 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * !!! * The backslash characters was special when it preceded a newline as part of * a substitution replacement pattern. For example, the input ":a\" would * failed immediately with an error, as the wasn't part of a substitution * replacement pattern. This implies a frightening integration of the editor * and the parser and/or the RE engine. There's no way I'm going to reproduce * those semantics. * * So, if backslashes are special, this code inserts the backslash and the next * character into the string, without regard for the character or the command * being entered. Since "\" was illegal historically (except for the one * special case), and the command will fail eventually, no historical scripts * should break (presuming they didn't depend on the failure mode itself or the * characters remaining when failure occurred. */ static int txt_dent(SCR *, TEXT *); static void txt_prompt(SCR *, TEXT *, CHAR_T, u_int32_t); /* * ex_txt -- * Get lines from the terminal for ex. * * PUBLIC: int ex_txt(SCR *, TEXTH *, CHAR_T, u_int32_t); */ int ex_txt(SCR *sp, TEXTH *tiqh, CHAR_T prompt, u_int32_t flags) { EVENT ev; GS *gp; TEXT ait, *ntp, *tp; carat_t carat_st; size_t cnt; int rval; int nochange; rval = 0; /* * Get a TEXT structure with some initial buffer space, reusing the * last one if it's big enough. (All TEXT bookkeeping fields default * to 0 -- text_init() handles this.) */ if (!TAILQ_EMPTY(tiqh)) { tp = TAILQ_FIRST(tiqh); if (TAILQ_NEXT(tp, q) || tp->lb_len < 32) { text_lfree(tiqh); goto newtp; } tp->len = 0; } else { newtp: if ((tp = text_init(sp, NULL, 0, 32)) == NULL) goto err; TAILQ_INSERT_HEAD(tiqh, tp, q); } /* Set the starting line number. */ tp->lno = sp->lno + 1; /* * If it's a terminal, set up autoindent, put out the prompt, and * set it up so we know we were suspended. Otherwise, turn off * the autoindent flag, as that requires less special casing below. * * XXX * Historic practice is that ^Z suspended command mode (but, because * it ran in cooked mode, it was unaffected by the autowrite option.) * On restart, any "current" input was discarded, whether in insert * mode or not, and ex was in command mode. This code matches historic * practice, but not 'cause it's easier. */ gp = sp->gp; if (F_ISSET(gp, G_SCRIPTED)) LF_CLR(TXT_AUTOINDENT); else { if (LF_ISSET(TXT_AUTOINDENT)) { LF_SET(TXT_EOFCHAR); if (v_txt_auto(sp, sp->lno, NULL, 0, tp)) goto err; } txt_prompt(sp, tp, prompt, flags); } for (carat_st = C_NOTSET, nochange = 0;;) { if (v_event_get(sp, &ev, 0, 0)) goto err; /* Deal with all non-character events. */ switch (ev.e_event) { case E_CHARACTER: break; case E_ERR: goto err; case E_REPAINT: case E_WRESIZE: continue; case E_EOF: rval = 1; /* FALLTHROUGH */ case E_INTERRUPT: /* * Handle EOF/SIGINT events by discarding partially * entered text and returning. EOF returns failure, * E_INTERRUPT returns success. */ goto notlast; default: v_event_err(sp, &ev); goto notlast; } /* * Deal with character events. * * Check to see if the character fits into the input buffer. * (Use tp->len, ignore overwrite and non-printable chars.) */ BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); switch (ev.e_value) { case K_CR: /* * !!! * Historically, 's in the command * weren't special, so the ex parser would return an * unknown command error message. However, if they * terminated the command if they were in a map. I'm * pretty sure this still isn't right, but it handles * what I've seen so far. */ if (!F_ISSET(&ev.e_ch, CH_MAPPED)) goto ins_ch; /* FALLTHROUGH */ case K_NL: /* * '\' can escape /. We * don't discard the backslash because we need it * to get the through the ex parser. */ if (LF_ISSET(TXT_BACKSLASH) && tp->len != 0 && tp->lb[tp->len - 1] == '\\') goto ins_ch; /* * CR returns from the ex command line. * * XXX * Terminate with a nul, needed by filter. */ if (LF_ISSET(TXT_CR)) { tp->lb[tp->len] = '\0'; goto done; } /* * '.' may terminate text input mode; free the current * TEXT. */ if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 && tp->lb[tp->len - 1] == '.') { notlast: TAILQ_REMOVE(tiqh, tp, q); text_free(tp); goto done; } /* Set up bookkeeping for the new line. */ if ((ntp = text_init(sp, NULL, 0, 32)) == NULL) goto err; ntp->lno = tp->lno + 1; /* * Reset the autoindent line value. 0^D keeps the ai * line from changing, ^D changes the level, even if * there were no characters in the old line. Note, if * using the current tp structure, use the cursor as * the length, the autoindent characters may have been * erased. */ if (LF_ISSET(TXT_AUTOINDENT)) { if (nochange) { nochange = 0; if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) goto err; free(ait.lb); } else if (v_txt_auto(sp, OOBLNO, tp, tp->len, ntp)) goto err; carat_st = C_NOTSET; } txt_prompt(sp, ntp, prompt, flags); /* * Swap old and new TEXT's, and insert the new TEXT * into the queue. */ tp = ntp; TAILQ_INSERT_TAIL(tiqh, tp, q); break; case K_CARAT: /* Delete autoindent chars. */ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat_st = C_CARATSET; goto ins_ch; case K_ZERO: /* Delete autoindent chars. */ if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat_st = C_ZEROSET; goto ins_ch; case K_CNTRLD: /* Delete autoindent char. */ /* * !!! * Historically, the ^D command took (but then ignored) * a count. For simplicity, we don't return it unless * it's the first character entered. The check for len * equal to 0 is okay, TXT_AUTOINDENT won't be set. */ if (LF_ISSET(TXT_CNTRLD)) { for (cnt = 0; cnt < tp->len; ++cnt) if (!isblank(tp->lb[cnt])) break; if (cnt == tp->len) { tp->len = 1; tp->lb[0] = ev.e_c; tp->lb[1] = '\0'; /* * Put out a line separator, in case * the command fails. */ (void)putchar('\n'); goto done; } } /* * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that * the EOF characters are discarded if there are other * characters to process in the line, i.e. if the EOF * is not the first character in the line. For this * reason, historic ex discarded the EOF characters, * even if occurring in the middle of the input line. * We match that historic practice. * * !!! * The test for discarding in the middle of the line is * done in the switch, because the CARAT forms are N+1, * not N. * * !!! * There's considerable magic to make the terminal code * return the EOF character at all. See that code for * details. */ if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0) continue; switch (carat_st) { case C_CARATSET: /* ^^D */ if (tp->len > tp->ai + 1) continue; /* Save the ai string for later. */ ait.lb = NULL; ait.lb_len = 0; BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai); memcpy(ait.lb, tp->lb, tp->ai); ait.ai = ait.len = tp->ai; carat_st = C_NOTSET; nochange = 1; goto leftmargin; case C_ZEROSET: /* 0^D */ if (tp->len > tp->ai + 1) continue; carat_st = C_NOTSET; leftmargin: (void)gp->scr_ex_adjust(sp, EX_TERM_CE); tp->ai = tp->len = 0; break; case C_NOTSET: /* ^D */ if (tp->len > tp->ai) continue; if (txt_dent(sp, tp)) goto err; break; default: abort(); } /* Clear and redisplay the line. */ (void)gp->scr_ex_adjust(sp, EX_TERM_CE); txt_prompt(sp, tp, prompt, flags); break; default: /* * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c. * * Silently eliminate any iscntrl() character that was * not already handled specially, except for and * . */ ins_ch: if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(ev.e_c) && ev.e_value != K_FORMFEED && ev.e_value != K_TAB) break; tp->lb[tp->len++] = ev.e_c; break; } } /* NOTREACHED */ done: return (rval); err: alloc_err: return (1); } /* * txt_prompt -- * Display the ex prompt, line number, ai characters. Characters had * better be printable by the terminal driver, but that's its problem, * not ours. */ static void txt_prompt(SCR *sp, TEXT *tp, CHAR_T prompt, u_int32_t flags) { /* Display the prompt. */ if (LF_ISSET(TXT_PROMPT)) (void)printf("%c", prompt); /* Display the line number. */ if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER)) (void)printf("%6lu ", (unsigned long)tp->lno); /* Print out autoindent string. */ if (LF_ISSET(TXT_AUTOINDENT)) (void)printf("%.*s", (int)tp->ai, tp->lb); (void)fflush(stdout); } /* * txt_dent -- * Handle ^D outdents. * * Ex version of vi/v_ntext.c:txt_dent(). See that code for the (usual) * ranting and raving. This is a fair bit simpler as ^T isn't special. */ static int txt_dent(SCR *sp, TEXT *tp) { unsigned long sw, ts; size_t cno, off, scno, spaces, tabs; ts = O_VAL(sp, O_TABSTOP); sw = O_VAL(sp, O_SHIFTWIDTH); /* Get the current screen column. */ for (off = scno = 0; off < tp->len; ++off) if (tp->lb[off] == '\t') scno += COL_OFF(scno, ts); else ++scno; /* Get the previous shiftwidth column. */ cno = scno--; scno -= scno % sw; /* * Since we don't know what comes before the character(s) being * deleted, we have to resolve the autoindent characters . The * example is a , which doesn't take up a full shiftwidth * number of columns because it's preceded by s. This is * easy to get if the user sets shiftwidth to a value less than * tabstop, and then uses ^T to indent, and ^D to outdent. * * Count up spaces/tabs needed to get to the target. */ cno = 0; tabs = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; cno + COL_OFF(cno, ts) <= scno; ++tabs) cno += COL_OFF(cno, ts); } spaces = scno - cno; /* Make sure there's enough room. */ BINC_RET(sp, tp->lb, tp->lb_len, tabs + spaces + 1); /* Adjust the final ai character count. */ tp->ai = tabs + spaces; /* Enter the replacement characters. */ for (tp->len = 0; tabs > 0; --tabs) tp->lb[tp->len++] = '\t'; for (; spaces > 0; --spaces) tp->lb[tp->len++] = ' '; return (0); } ================================================ FILE: ex/ex_undo.c ================================================ /* $OpenBSD: ex_undo.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" /* * ex_undo -- u * Undo the last change. * * PUBLIC: int ex_undo(SCR *, EXCMD *); */ int ex_undo(SCR *sp, EXCMD *cmdp) { EXF *ep; MARK m; /* * !!! * Historic undo always set the previous context mark. */ m.lno = sp->lno; m.cno = sp->cno; if (mark_set(sp, ABSMARK1, &m, 1)) return (1); /* * !!! * Multiple undo isn't available in ex, as there's no '.' command. * Whether 'u' is undo or redo is toggled each time, unless there * was a change since the last undo, in which case it's an undo. */ ep = sp->ep; if (!F_ISSET(ep, F_UNDO)) { F_SET(ep, F_UNDO); ep->lundo = FORWARD; } switch (ep->lundo) { case BACKWARD: if (log_forward(sp, &m)) return (1); ep->lundo = FORWARD; break; case FORWARD: if (log_backward(sp, &m)) return (1); ep->lundo = BACKWARD; break; case NOTSET: abort(); } sp->lno = m.lno; sp->cno = m.cno; return (0); } ================================================ FILE: ex/ex_usage.c ================================================ /* $OpenBSD: ex_usage.c,v 1.10 2018/07/13 09:02:07 krw Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_help -- :help * Display help message. * * PUBLIC: int ex_help(SCR *, EXCMD *); */ int ex_help(SCR *sp, EXCMD *cmdp) { (void)ex_puts(sp, "To see the list of vi commands, enter \":viusage\"\n"); (void)ex_puts(sp, "To see the list of ex commands, enter \":exusage\"\n"); (void)ex_puts(sp, "For an ex command usage statement enter \":exusage [cmd]\"\n"); (void)ex_puts(sp, "For a vi key usage statement enter \":viusage [key]\"\n"); (void)ex_puts(sp, "To exit, enter \":q!\"\n"); return (0); } /* * ex_usage -- :exusage [cmd] * Display ex usage strings. * * PUBLIC: int ex_usage(SCR *, EXCMD *); */ int ex_usage(SCR *sp, EXCMD *cmdp) { ARGS *ap; EXCMDLIST const *cp; int newscreen; switch (cmdp->argc) { case 1: ap = cmdp->argv[0]; if (isupper(ap->bp[0])) { newscreen = 1; ap->bp[0] = tolower(ap->bp[0]); } else newscreen = 0; for (cp = cmds; cp->name != NULL && memcmp(ap->bp, cp->name, ap->len); ++cp); if (cp->name == NULL || (newscreen && !F_ISSET(cp, E_NEWSCREEN))) { if (newscreen) ap->bp[0] = toupper(ap->bp[0]); (void)ex_printf(sp, "The %.*s command is unknown\n", (int)ap->len, ap->bp); } else { (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); /* * !!! * The "visual" command has two modes, one from ex, * one from the vi colon line. Don't ask. */ if (cp != &cmds[C_VISUAL_EX] && cp != &cmds[C_VISUAL_VI]) break; if (cp == &cmds[C_VISUAL_EX]) cp = &cmds[C_VISUAL_VI]; else cp = &cmds[C_VISUAL_EX]; (void)ex_printf(sp, "Command: %s\n Usage: %s\n", cp->help, cp->usage); } break; case 0: for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) (void)ex_printf(sp, "%*s: %s\n", MAXCMDNAMELEN, /* The ^D command has an unprintable name. */ cp == &cmds[C_SCROLL] ? "^D" : cp->name, cp->help); break; default: abort(); } return (0); } /* * ex_viusage -- :viusage [key] * Display vi usage strings. * * PUBLIC: int ex_viusage(SCR *, EXCMD *); */ int ex_viusage(SCR *sp, EXCMD *cmdp) { VIKEYS const *kp; int key; switch (cmdp->argc) { case 1: if (cmdp->argv[0]->len != 1) { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } key = cmdp->argv[0]->bp[0]; if (key > MAXVIKEY) goto nokey; /* Special case: '[' and ']' commands. */ if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key) goto nokey; /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->usage == NULL) nokey: (void)ex_printf(sp, "The %s key has no current meaning\n", KEY_NAME(sp, key)); else (void)ex_printf(sp, " Key:%s%s\nUsage: %s\n", isblank(*kp->help) ? "" : " ", kp->help, kp->usage); break; case 0: for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) { /* Special case: ~ command. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; else kp = &vikeys[key]; if (kp->help != NULL) (void)ex_printf(sp, "%s\n", kp->help); } break; default: abort(); } return (0); } ================================================ FILE: ex/ex_util.c ================================================ /* $OpenBSD: ex_util.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_cinit -- * Create an EX command structure. * * PUBLIC: void ex_cinit(EXCMD *, int, int, recno_t, recno_t, int, ARGS **); */ void ex_cinit(EXCMD *cmdp, int cmd_id, int naddr, recno_t lno1, recno_t lno2, int force, ARGS **ap) { memset(cmdp, 0, sizeof(EXCMD)); cmdp->cmd = &cmds[cmd_id]; cmdp->addrcnt = naddr; cmdp->addr1.lno = lno1; cmdp->addr2.lno = lno2; cmdp->addr1.cno = cmdp->addr2.cno = 1; if (force) cmdp->iflags |= E_C_FORCE; cmdp->argc = 0; if ((cmdp->argv = ap) != NULL) cmdp->argv[0] = NULL; } /* * ex_cadd -- * Add an argument to an EX command structure. * * PUBLIC: void ex_cadd(EXCMD *, ARGS *, char *, size_t); */ void ex_cadd(EXCMD *cmdp, ARGS *ap, char *arg, size_t len) { cmdp->argv[cmdp->argc] = ap; ap->bp = arg; ap->len = len; cmdp->argv[++cmdp->argc] = NULL; } /* * ex_getline -- * Return a line from the file. * * PUBLIC: int ex_getline(SCR *, FILE *, size_t *); */ int ex_getline(SCR *sp, FILE *fp, size_t *lenp) { EX_PRIVATE *exp; size_t off; int ch; char *p; exp = EXP(sp); for (errno = 0, off = 0, p = exp->ibp;;) { if (off >= exp->ibp_len) { BINC_RET(sp, exp->ibp, exp->ibp_len, off + 1); p = exp->ibp + off; } if ((ch = getc(fp)) == EOF && !feof(fp)) { if (errno == EINTR) { errno = 0; clearerr(fp); continue; } return (1); } if (ch == EOF || ch == '\n') { if (ch == EOF && !off) return (1); *lenp = off; return (0); } *p++ = ch; ++off; } /* NOTREACHED */ } /* * ex_ncheck -- * Check for more files to edit. * * PUBLIC: int ex_ncheck(SCR *, int); */ int ex_ncheck(SCR *sp, int force) { char **ap; /* * !!! * Historic practice: quit! or two quit's done in succession * (where ZZ counts as a quit) didn't check for other files. */ if (!force && sp->ccnt != sp->q_ccnt + 1 && sp->cargv != NULL && sp->cargv[1] != NULL) { sp->q_ccnt = sp->ccnt; for (ap = sp->cargv + 1; *ap != NULL; ++ap); msgq(sp, M_ERR, "%d more files to edit", (ap - sp->cargv) - 1); return (1); } return (0); } /* * ex_init -- * Init the screen for ex. * * PUBLIC: int ex_init(SCR *); */ int ex_init(SCR *sp) { GS *gp; gp = sp->gp; if (gp->scr_screen(sp, SC_EX)) return (1); (void)gp->scr_attr(sp, SA_ALTERNATE, 0); sp->rows = O_VAL(sp, O_LINES); sp->cols = O_VAL(sp, O_COLUMNS); F_CLR(sp, SC_VI); F_SET(sp, SC_EX | SC_SCR_EX); return (0); } /* * ex_emsg -- * Display a few common ex and vi error messages. * * PUBLIC: void ex_emsg(SCR *, char *, exm_t); */ void ex_emsg(SCR *sp, char *p, exm_t which) { switch (which) { case EXM_EMPTYBUF: msgq(sp, M_ERR, "Buffer %s is empty", p); break; case EXM_FILECOUNT: msgq_str(sp, M_ERR, p, "%s: expanded into too many file names"); break; case EXM_NOCANON: msgq(sp, M_ERR, "The %s command requires the ex terminal interface", p); break; case EXM_NOCANON_F: msgq(sp, M_ERR, "That form of %s requires the ex terminal interface", p); break; case EXM_NOFILEYET: if (p == NULL) msgq(sp, M_ERR, "Command failed, no file read in yet."); else msgq(sp, M_ERR, "The %s command requires that a file have already been read in", p); break; case EXM_NOPREVBUF: msgq(sp, M_ERR, "No previous buffer to execute"); break; case EXM_NOPREVRE: msgq(sp, M_ERR, "No previous regular expression"); break; case EXM_NOSUSPEND: msgq(sp, M_ERR, "This screen may not be suspended"); break; case EXM_SECURE: msgq(sp, M_ERR, "The %s command is not supported when the secure edit option is set", p); break; case EXM_SECURE_F: msgq(sp, M_ERR, "That form of %s is not supported when the secure edit option is set", p); break; case EXM_USAGE: msgq(sp, M_ERR, "Usage: %s", p); break; } } ================================================ FILE: ex/ex_version.c ================================================ /* $OpenBSD: ex_version.c,v 1.10 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" #include "version.h" /* * ex_version -- :version * Display the program version. * * PUBLIC: int ex_version(SCR *, EXCMD *); */ int ex_version(SCR *sp, EXCMD *cmdp) { msgq(sp, M_XINFO, VI_VERSION); return (0); } ================================================ FILE: ex/ex_visual.c ================================================ /* $OpenBSD: ex_visual.c,v 1.10 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "../vi/vi.h" /* * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] * Switch to visual mode. * * PUBLIC: int ex_visual(SCR *, EXCMD *); */ int ex_visual(SCR *sp, EXCMD *cmdp) { SCR *tsp; size_t len; int pos; char buf[256]; /* If open option off, disallow visual command. */ if (!O_ISSET(sp, O_OPEN)) { msgq(sp, M_ERR, "The visual command requires that the open option be set"); return (1); } /* Move to the address. */ sp->lno = cmdp->addr1.lno == 0 ? 1 : cmdp->addr1.lno; /* * Push a command based on the line position flags. If no * flag specified, the line goes at the top of the screen. */ switch (FL_ISSET(cmdp->iflags, E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) { case E_C_CARAT: pos = '^'; break; case E_C_DASH: pos = '-'; break; case E_C_DOT: pos = '.'; break; case E_C_PLUS: pos = '+'; break; default: sp->frp->lno = sp->lno; sp->frp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); F_SET(sp->frp, FR_CURSORSET); goto nopush; } if (FL_ISSET(cmdp->iflags, E_C_COUNT)) len = snprintf(buf, sizeof(buf), "%luz%c%lu", (unsigned long)sp->lno, pos, cmdp->count); else len = snprintf(buf, sizeof(buf), "%luz%c", (unsigned long)sp->lno, pos); if (len >= sizeof(buf)) len = sizeof(buf) - 1; (void)v_event_push(sp, NULL, buf, len, CH_NOMAP | CH_QUOTED); /* * !!! * Historically, if no line address was specified, the [p#l] flags * caused the cursor to be moved to the last line of the file, which * was then positioned as described above. This seems useless, so * I haven't implemented it. */ switch (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) { case E_C_HASH: O_SET(sp, O_NUMBER); break; case E_C_LIST: O_SET(sp, O_LIST); break; case E_C_PRINT: break; } nopush: /* * !!! * You can call the visual part of the editor from within an ex * global command. * * XXX * Historically, undoing a visual session was a single undo command, * i.e. you could undo all of the changes you made in visual mode. * We don't get this right; I'm waiting for the new logging code to * be available. * * It's explicit, don't have to wait for the user, unless there's * already a reason to wait. */ if (!F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(sp, SC_EX_WAIT_NO); if (F_ISSET(sp, SC_EX_GLOBAL)) { /* * When the vi screen(s) exit, we don't want to lose our hold * on this screen or this file, otherwise we're going to fail * fairly spectacularly. */ ++sp->refcnt; ++sp->ep->refcnt; /* * Fake up a screen pointer -- vi doesn't get to change our * underlying file, regardless. */ tsp = sp; if (vi(&tsp)) return (1); /* * !!! * Historically, if the user exited the vi screen(s) using an * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if * they exited vi using the Q command that ex continued. Some * early versions of nvi continued in ex regardless, but users * didn't like the semantic. * * Reset the screen. */ if (ex_init(sp)) return (1); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); } else { F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); } return (0); } ================================================ FILE: ex/ex_write.c ================================================ /* $OpenBSD: ex_write.c,v 1.13 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" enum which {WN, WQ, WRITE, XIT}; static int exwr(SCR *, EXCMD *, enum which); /* * ex_wn -- :wn[!] [>>] [file] * Write to a file and switch to the next one. * * PUBLIC: int ex_wn(SCR *, EXCMD *); */ int ex_wn(SCR *sp, EXCMD *cmdp) { if (exwr(sp, cmdp, WN)) return (1); if (file_m3(sp, 0)) return (1); /* The file name isn't a new file to edit. */ cmdp->argc = 0; return (ex_next(sp, cmdp)); } /* * ex_wq -- :wq[!] [>>] [file] * Write to a file and quit. * * PUBLIC: int ex_wq(SCR *, EXCMD *); */ int ex_wq(SCR *sp, EXCMD *cmdp) { int force; if (exwr(sp, cmdp, WQ)) return (1); if (file_m3(sp, 0)) return (1); force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } /* * ex_write -- :write[!] [>>] [file] * :write [!] [cmd] * Write to a file. * * PUBLIC: int ex_write(SCR *, EXCMD *); */ int ex_write(SCR *sp, EXCMD *cmdp) { return (exwr(sp, cmdp, WRITE)); } /* * ex_xit -- :x[it]! [file] * Write out any modifications and quit. * * PUBLIC: int ex_xit(SCR *, EXCMD *); */ int ex_xit(SCR *sp, EXCMD *cmdp) { int force; NEEDFILE(sp, cmdp); if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT)) return (1); if (file_m3(sp, 0)) return (1); force = FL_ISSET(cmdp->iflags, E_C_FORCE); if (ex_ncheck(sp, force)) return (1); F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); return (0); } /* * exwr -- * The guts of the ex write commands. */ static int exwr(SCR *sp, EXCMD *cmdp, enum which cmd) { MARK rm; int flags; char *name, *p = NULL; NEEDFILE(sp, cmdp); /* All write commands can have an associated '!'. */ LF_INIT(FS_POSSIBLE); if (FL_ISSET(cmdp->iflags, E_C_FORCE)) LF_SET(FS_FORCE); /* Skip any leading whitespace. */ if (cmdp->argc != 0) for (p = cmdp->argv[0]->bp; isblank(*p); ++p) ; /* If "write !" it's a pipe to a utility. */ if (cmdp->argc != 0 && cmd == WRITE && *p == '!') { /* Secure means no shell access. */ if (O_ISSET(sp, O_SECURE)) { ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } /* Expand the argument. */ for (++p; isblank(*p); ++p); if (*p == '\0') { ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); return (1); } if (argv_exp1(sp, cmdp, p, strlen(p), 1)) return (1); /* * Historically, vi waited after a write filter even if there * wasn't any output from the command. People complained when * nvi waited only if there was output, wanting the visual cue * that the program hadn't written anything. */ F_SET(sp, SC_EX_WAIT_YES); /* * !!! * Ignore the return cursor position, the cursor doesn't * move. */ if (ex_filter(sp, cmdp, &cmdp->addr1, &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE)) return (1); /* Ex terminates with a bang, even if the command fails. */ if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) (void)ex_puts(sp, "!\n"); return (0); } /* Set the FS_ALL flag if we're writing the entire file. */ if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1)) LF_SET(FS_ALL); /* If "write >>" it's an append to a file. */ if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') { LF_SET(FS_APPEND); /* Skip ">>" and whitespace. */ for (p += 2; isblank(*p); ++p); } /* If no other arguments, just write the file back. */ if (cmdp->argc == 0 || *p == '\0') return (file_write(sp, &cmdp->addr1, &cmdp->addr2, NULL, flags)); /* Build an argv so we get an argument count and file expansion. */ if (argv_exp2(sp, cmdp, p, strlen(p))) return (1); /* * 0 args: impossible. * 1 args: impossible (I hope). * 2 args: read it. * >2 args: object, too many args. * * The 1 args case depends on the argv_sexp() function refusing * to return success without at least one non-blank character. */ switch (cmdp->argc) { case 0: case 1: abort(); /* NOTREACHED */ case 2: name = cmdp->argv[1]->bp; /* * !!! * Historically, the read and write commands renamed * "unnamed" files, or, if the file had a name, set * the alternate file name. */ if (F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(sp->frp, FR_EXNAMED)) { if ((p = v_strdup(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) { free(sp->frp->name); sp->frp->name = p; } /* * The file has a real name, it's no longer a * temporary, clear the temporary file flags. * * !!! * If we're writing the whole file, FR_NAMECHANGE * will be cleared by the write routine -- this is * historic practice. */ F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); /* Notify the screen. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } else set_alt_name(sp, name); break; default: ex_emsg(sp, p, EXM_FILECOUNT); return (1); } return (file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags)); } /* * ex_writefp -- * Write a range of lines to a FILE *. * * PUBLIC: int ex_writefp(SCR *, * PUBLIC: char *, FILE *, MARK *, MARK *, unsigned long *, unsigned long *, int); */ int ex_writefp(SCR *sp, char *name, FILE *fp, MARK *fm, MARK *tm, unsigned long *nlno, unsigned long *nch, int silent) { struct stat sb; GS *gp; unsigned long ccnt; /* XXX: can't print off_t portably. */ recno_t fline, tline, lcnt; size_t len; int rval; char *msg, *p; gp = sp->gp; fline = fm->lno; tline = tm->lno; if (nlno != NULL) { *nch = 0; *nlno = 0; } /* * The vi filter code has multiple processes running simultaneously, * and one of them calls ex_writefp(). The "unsafe" function calls * in this code are to db_get() and msgq(). Db_get() is safe, see * the comment in ex_filter.c:ex_filter() for details. We don't call * msgq if the multiple process bit in the EXF is set. * * !!! * Historic vi permitted files of 0 length to be written. However, * since the way vi got around dealing with "empty" files was to * always have a line in the file no matter what, it wrote them as * files of a single, empty line. We write empty files. * * "Alex, I'll take vi trivia for $1000." */ ccnt = 0; lcnt = 0; msg = "Writing..."; if (tline != 0) for (; fline <= tline; ++fline, ++lcnt) { /* Caller has to provide any interrupt message. */ if ((lcnt + 1) % INTERRUPT_CHECK == 0) { if (INTERRUPTED(sp)) break; if (!silent) { gp->scr_busy(sp, msg, msg == NULL ? BUSY_UPDATE : BUSY_ON); msg = NULL; } } if (db_get(sp, fline, DBG_FATAL, &p, &len)) goto err; if (fwrite(p, 1, len, fp) != len) goto err; ccnt += len; if (putc('\n', fp) != '\n') break; ++ccnt; } if (fflush(fp)) goto err; /* * XXX * I don't trust NFS -- check to make sure that we're talking to * a regular file and sync so that NFS is forced to flush. */ if (!fstat(fileno(fp), &sb) && S_ISREG(sb.st_mode) && fsync(fileno(fp))) goto err; if (fclose(fp)) { fp = NULL; goto err; } rval = 0; if (0) { err: if (!F_ISSET(sp->ep, F_MULTILOCK)) msgq_str(sp, M_SYSERR, name, "%s"); if (fp != NULL) (void)fclose(fp); rval = 1; } if (!silent) gp->scr_busy(sp, NULL, BUSY_OFF); /* Report the possibly partial transfer. */ if (nlno != NULL) { *nch = ccnt; *nlno = lcnt; } return (rval); } ================================================ FILE: ex/ex_yank.c ================================================ /* $OpenBSD: ex_yank.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include "../common/common.h" /* * ex_yank -- :[line [,line]] ya[nk] [buffer] [count] * Yank the lines into a buffer. * * PUBLIC: int ex_yank(SCR *, EXCMD *); */ int ex_yank(SCR *sp, EXCMD *cmdp) { NEEDFILE(sp, cmdp); /* * !!! * Historically, yanking lines in ex didn't count toward the * number-of-lines-yanked report. */ return (cut(sp, FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)); } ================================================ FILE: ex/ex_z.c ================================================ /* $OpenBSD: ex_z.c,v 1.8 2015/03/29 01:04:23 bcallah Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" /* * ex_z -- :[line] z [^-.+=] [count] [flags] * Adjust window. * * PUBLIC: int ex_z(SCR *, EXCMD *); */ int ex_z(SCR *sp, EXCMD *cmdp) { MARK mark_abs; recno_t cnt, equals, lno; int eofcheck; NEEDFILE(sp, cmdp); /* * !!! * If no count specified, use either two times the size of the * scrolling region, or the size of the window option. POSIX * 1003.2 claims that the latter is correct, but historic ex/vi * documentation and practice appear to use the scrolling region. * I'm using the window size as it means that the entire screen * is used instead of losing a line to roundoff. Note, we drop * a line from the cnt if using the window size to leave room for * the next ex prompt. */ if (FL_ISSET(cmdp->iflags, E_C_COUNT)) cnt = cmdp->count; else cnt = O_VAL(sp, O_WINDOW) - 1; equals = 0; eofcheck = 0; lno = cmdp->addr1.lno; switch (FL_ISSET(cmdp->iflags, E_C_CARAT | E_C_DASH | E_C_DOT | E_C_EQUAL | E_C_PLUS)) { case E_C_CARAT: /* Display cnt * 2 before the line. */ eofcheck = 1; if (lno > cnt * 2) cmdp->addr1.lno = (lno - cnt * 2) + 1; else cmdp->addr1.lno = 1; cmdp->addr2.lno = (cmdp->addr1.lno + cnt) - 1; break; case E_C_DASH: /* Line goes at the bottom of the screen. */ cmdp->addr1.lno = lno > cnt ? (lno - cnt) + 1 : 1; cmdp->addr2.lno = lno; break; case E_C_DOT: /* Line goes in the middle of the screen. */ /* * !!! * Historically, the "middleness" of the line overrode the * count, so that "3z.19" or "3z.20" would display the first * 12 lines of the file, i.e. (N - 1) / 2 lines before and * after the specified line. */ eofcheck = 1; cnt = (cnt - 1) / 2; cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; cmdp->addr2.lno = lno + cnt; /* * !!! * Historically, z. set the absolute cursor mark. */ mark_abs.lno = sp->lno; mark_abs.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &mark_abs, 1); break; case E_C_EQUAL: /* Center with hyphens. */ /* * !!! * Strangeness. The '=' flag is like the '.' flag (see the * above comment, it applies here as well) but with a special * little hack. Print out lines of hyphens before and after * the specified line. Additionally, the cursor remains set * on that line. */ eofcheck = 1; cnt = (cnt - 1) / 2; cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; cmdp->addr2.lno = lno - 1; if (ex_pr(sp, cmdp)) return (1); (void)ex_puts(sp, "----------------------------------------\n"); cmdp->addr2.lno = cmdp->addr1.lno = equals = lno; if (ex_pr(sp, cmdp)) return (1); (void)ex_puts(sp, "----------------------------------------\n"); cmdp->addr1.lno = lno + 1; cmdp->addr2.lno = (lno + cnt) - 1; break; default: /* If no line specified, move to the next one. */ if (F_ISSET(cmdp, E_ADDR_DEF)) ++lno; /* FALLTHROUGH */ case E_C_PLUS: /* Line goes at the top of the screen. */ eofcheck = 1; cmdp->addr1.lno = lno; cmdp->addr2.lno = (lno + cnt) - 1; break; } if (eofcheck) { if (db_last(sp, &lno)) return (1); if (cmdp->addr2.lno > lno) cmdp->addr2.lno = lno; } if (ex_pr(sp, cmdp)) return (1); if (equals) sp->lno = equals; return (0); } ================================================ FILE: ex/script.h ================================================ /* $OpenBSD: script.h,v 1.4 2014/11/12 16:29:04 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)script.h 10.2 (Berkeley) 3/6/96 */ struct _script { pid_t sh_pid; /* Shell pid. */ int sh_master; /* Master pty fd. */ int sh_slave; /* Slave pty fd. */ char *sh_prompt; /* Prompt. */ size_t sh_prompt_len; /* Prompt length. */ char sh_name[64]; /* Pty name */ struct winsize sh_win; /* Window size. */ struct termios sh_term; /* Terminal information. */ }; ================================================ FILE: ex/tag.h ================================================ /* $OpenBSD: tag.h,v 1.7 2015/11/19 07:53:31 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 1994, 1996 * Rob Mayoff. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)tag.h 10.5 (Berkeley) 5/15/96 */ /* * Tag file information. One of these is maintained per tag file, linked * from the EXPRIVATE structure. */ struct _tagf { /* Tag files. */ TAILQ_ENTRY(_tagf) q; /* Linked list of tag files. */ char *name; /* Tag file name. */ int errnum; /* Errno. */ #define TAGF_ERR 0x01 /* Error occurred. */ #define TAGF_ERR_WARN 0x02 /* Error reported. */ u_int8_t flags; }; /* * Tags are structured internally as follows: * * +----+ +----+ +----+ +----+ * | EP | -> | Q1 | <-- | T1 | <-- | T2 | * +----+ +----+ --> +----+ --> +----+ * | * +----+ +----+ * | Q2 | <-- | T1 | * +----+ --> +----+ * | * +----+ +----+ * | Q3 | <-- | T1 | * +----+ --> +----+ * * Each Q is a TAGQ, or tag "query", which is the result of one tag. * Each Q references one or more TAG's, or tagged file locations. * * tag: put a new Q at the head (^]) * tagnext: T1 -> T2 inside Q (^N) * tagprev: T2 -> T1 inside Q (^P) * tagpop: discard Q (^T) * tagtop: discard all Q */ struct _tag { /* Tag list. */ TAILQ_ENTRY(_tag) q; /* Linked list of tags. */ /* Tag pop/return information. */ FREF *frp; /* Saved file. */ recno_t lno; /* Saved line number. */ size_t cno; /* Saved column number. */ char *fname; /* Filename. */ size_t fnlen; /* Filename length. */ recno_t slno; /* Search line number. */ char *search; /* Search string. */ size_t slen; /* Search string length. */ char buf[1]; /* Variable length buffer. */ }; struct _tagq { /* Tag queue. */ TAILQ_ENTRY(_tagq) q; /* Linked list of tag queues. */ /* This queue's tag list. */ TAILQ_HEAD(_tagqh, _tag) tagq; TAG *current; /* Current TAG within the queue. */ char *tag; /* Tag string. */ size_t tlen; /* Tag string length. */ u_int8_t flags; char buf[1]; /* Variable length buffer. */ }; ================================================ FILE: ex/version.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _VERSION_H # define _VERSION_H # define VI_VERSION \ "Version 7.8.33-dev (OpenVi) 08/23/2025" #endif /* ifndef _VERSION_H */ ================================================ FILE: include/bitstring.h ================================================ /* $OpenBSD: bitstring.h,v 1.6 2020/05/10 00:56:06 guenther Exp $ */ /* $NetBSD: bitstring.h,v 1.5 1997/05/14 15:49:55 pk Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Paul Vixie. * * 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. * * @(#)bitstring.h 8.1 (Berkeley) 7/19/93 */ #ifndef _BITSTRING_H_ # define _BITSTRING_H_ /* modified for SV/AT and bitstring bugfix by M.R.Murphy, 11oct91 * bitstr_size changed gratuitously, but shorter * bit_alloc spelling error fixed * the following were efficient, but didn't work, they've been made to * work, but are no longer as efficient :-) * bit_nclear, bit_nset, bit_ffc, bit_ffs */ typedef unsigned char bitstr_t; /* internal macros */ /* byte of the bitstring bit is in */ # define _bit_byte(bit) \ ((bit) >> 3) /* mask for the bit within its byte */ # define _bit_mask(bit) \ (1 << ((bit)&0x7)) /* external macros */ /* bytes in a bitstring of nbits bits */ # define bitstr_size(nbits) \ (((nbits) + 7) >> 3) /* allocate a bitstring */ # define bit_alloc(nbits) \ (bitstr_t *)calloc((size_t)bitstr_size(nbits), sizeof(bitstr_t)) /* allocate a bitstring on the stack */ # define bit_decl(name, nbits) \ ((name)[bitstr_size(nbits)]) /* is bit N of bitstring name set? */ # define bit_test(name, bit) \ ((name)[_bit_byte(bit)] & _bit_mask(bit)) /* set bit N of bitstring name */ # define bit_set(name, bit) \ ((name)[_bit_byte(bit)] |= _bit_mask(bit)) /* clear bit N of bitstring name */ # define bit_clear(name, bit) \ ((name)[_bit_byte(bit)] &= ~_bit_mask(bit)) /* clear bits start ... stop in bitstring */ # define bit_nclear(name, start, stop) do { \ register bitstr_t *__name = (name); \ register int __start = (start), __stop = (stop); \ while (__start <= __stop) { \ bit_clear(__name, __start); \ __start++; \ } \ } while(0) /* set bits start ... stop in bitstring */ # define bit_nset(name, start, stop) do { \ register bitstr_t *__name = (name); \ register int __start = (start), __stop = (stop); \ while (__start <= __stop) { \ bit_set(__name, __start); \ __start++; \ } \ } while(0) /* find first bit clear in name */ # define bit_ffc(name, nbits, value) do { \ register bitstr_t *__name = (name); \ register int __bit, __nbits = (nbits), __value = -1; \ for (__bit = 0; __bit < __nbits; ++__bit) \ if (!bit_test(__name, __bit)) { \ __value = __bit; \ break; \ } \ *(value) = __value; \ } while(0) /* find first bit set in name */ # define bit_ffs(name, nbits, value) do { \ register bitstr_t *__name = (name); \ register int __bit, __nbits = (nbits), __value = -1; \ for (__bit = 0; __bit < __nbits; ++__bit) \ if (bit_test(__name, __bit)) { \ __value = __bit; \ break; \ } \ *(value) = __value; \ } while(0) #endif /* !_BITSTRING_H_ */ ================================================ FILE: include/bsd_db.h ================================================ /* $OpenBSD: db.h,v 1.12 2015/10/17 21:48:42 guenther Exp $ */ /* $NetBSD: db.h,v 1.13 1994/10/26 00:55:48 cgd Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)db.h 8.7 (Berkeley) 6/16/94 */ #ifndef _DB_H_ # define _DB_H_ # include "../include/compat.h" # include # include # undef open # define RET_ERROR -1 /* Return values. */ # define RET_SUCCESS 0 # define RET_SPECIAL 1 # define MAX_PAGE_NUMBER 0xffffffff /* >= # of pages in a file */ typedef u_int32_t pgno_t; # define MAX_PAGE_OFFSET 65535 /* >= # of bytes in a page */ typedef u_int16_t indx_t; # define MAX_REC_NUMBER 0xffffffff /* >= # of records in a tree */ typedef u_int32_t recno_t; /* Key/data structure -- a Data-Base Thang. */ typedef struct { void *data; /* data */ size_t size; /* data length */ } DBT; /* Routine flags. */ # define R_CURSOR 1 /* del, put, seq */ # define __R_UNUSED 2 /* UNUSED */ # define R_FIRST 3 /* seq */ # define R_IAFTER 4 /* put (RECNO) */ # define R_IBEFORE 5 /* put (RECNO) */ # define R_LAST 6 /* seq (BTREE, RECNO) */ # define R_NEXT 7 /* seq */ # define R_NOOVERWRITE 8 /* put */ # define R_PREV 9 /* seq (BTREE, RECNO) */ # define R_SETCURSOR 10 /* put (RECNO) */ # define R_RECNOSYNC 11 /* sync (RECNO) */ typedef enum { DB_BTREE, DB_HASH, DB_RECNO } DBTYPE; /* * !!! * The following flags are included in the dbopen(3) call as part of the * open(2) flags. In order to avoid conflicts with the open flags, start * at the top of the 16 or 32-bit number space and work our way down. If * the open flags were significantly expanded in the future, it could be * a problem. Wish I'd left another flags word in the dbopen call. * * !!! * None of this stuff is implemented yet. The only reason that it's here * is so that the access methods can skip copying the key/data pair when * the DB_LOCK flag isn't set. */ # if UINT_MAX > 65535 # define DB_LOCK 0x20000000 /* Do locking. */ # define DB_SHMEM 0x40000000 /* Use shared memory. */ # define DB_TXN 0x80000000 /* Do transactions. */ # else # define DB_LOCK 0x2000 /* Do locking. */ # define DB_SHMEM 0x4000 /* Use shared memory. */ # define DB_TXN 0x8000 /* Do transactions. */ # endif /* if UINT_MAX > 65535 */ /* Access method description structure. */ typedef struct __db { DBTYPE type; /* Underlying db type. */ int (*close)(struct __db *); int (*del)(const struct __db *, const DBT *, unsigned int); int (*get)(const struct __db *, const DBT *, DBT *, unsigned int); int (*put)(const struct __db *, DBT *, const DBT *, unsigned int); int (*seq)(const struct __db *, DBT *, DBT *, unsigned int); int (*sync)(const struct __db *, unsigned int); void *internal; /* Access method private. */ int (*fd)(const struct __db *); } DB; # define BTREEMAGIC 0x053162 # define BTREEVERSION 3 /* Structure used to pass parameters to the btree routines. */ typedef struct { # define R_DUP 0x01 /* duplicate keys */ unsigned long flags; /* ... */ unsigned int cachesize; /* bytes to cache */ int maxkeypage; /* maximum keys per page */ int minkeypage; /* minimum keys per page */ unsigned int psize; /* page size */ int (*compare) /* comparison function */ (const DBT *, const DBT *); /* ... */ size_t (*prefix) /* prefix function */ (const DBT *, const DBT *); /* ... */ int lorder; /* byte order */ } BTREEINFO; # define HASHMAGIC 0x061561 # define HASHVERSION 2 /* Structure used to pass parameters to the hashing routines. */ typedef struct { unsigned int bsize; /* bucket size */ unsigned int ffactor; /* fill factor */ unsigned int nelem; /* number of elements */ unsigned int cachesize; /* bytes to cache */ u_int32_t /* hash function */ (*hash)(const void *, size_t); /* ... */ int lorder; /* byte order */ } HASHINFO; /* Structure used to pass parameters to the record routines. */ typedef struct { # define R_FIXEDLEN 0x01 /* fixed-length records */ # define R_NOKEY 0x02 /* key not required */ # define R_SNAPSHOT 0x04 /* snapshot the input */ unsigned long flags; /* ... */ unsigned int cachesize; /* bytes to cache */ unsigned int psize; /* page size */ int lorder; /* byte order */ size_t reclen; /* record length */ /* (fixed-length records) */ unsigned char bval; /* delimiting byte */ /* (variable-length records) */ char *bfname; /* btree file name */ } RECNOINFO; DB *dbopen(const char *, int, int, DBTYPE, const void *); #endif /* !_DB_H_ */ ================================================ FILE: include/bsd_err.h ================================================ /* $OpenBSD: err.h,v 1.13 2015/08/31 02:53:56 guenther Exp $ */ /* $NetBSD: err.h,v 1.11 1994/10/26 00:55:52 cgd Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)err.h 8.1 (Berkeley) 6/2/93 */ #ifndef _COMPAT_ERR_H_ # define _COMPAT_ERR_H_ # include /* for va_list */ void openbsd_errc(int, int, const char *, ...) __attribute__((__format__ (printf, 3, 4))); void openbsd_verrc(int, int, const char *, va_list) __attribute__((__format__ (printf, 3, 0))); void openbsd_warnc(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); void openbsd_vwarnc(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0))); #endif /* !_COMPAT_ERR_H_ */ ================================================ FILE: include/bsd_fcntl.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include_next #ifndef _COMPAT_FCNTL_H_ # define _COMPAT_FCNTL_H_ # define open openbsd_open # ifdef O_EXLOCK # undef O_EXLOCK # endif /* ifdef O_EXLOCK */ # define O_EXLOCK 10000000 # ifdef O_SHLOCK # undef O_SHLOCK # endif /* ifdef O_SHLOCK */ # define O_SHLOCK 20000000 int openbsd_open(const char *, int, ...); #endif /* !_COMPAT_FCNTL_H_ */ ================================================ FILE: include/bsd_regex.h ================================================ /* $OpenBSD: regex.h,v 1.7 2012/12/05 23:19:57 deraadt Exp $ */ /* $NetBSD: regex.h,v 1.4.6.1 1996/06/10 18:57:07 explorer Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992 Henry Spencer. * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer of the University of Toronto. * * 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. * * @(#)regex.h 8.1 (Berkeley) 6/2/93 */ #ifndef _REGEX_H_ # define _REGEX_H_ # include # define regoff_t openbsd_regoff_t # define regex_t openbsd_regex_t # define regmatch_t openbsd_regmatch_t /* types */ typedef off_t regoff_t; typedef struct { int re_magic; size_t re_nsub; /* number of parenthesized subexpressions */ const char *re_endp; /* end pointer for REG_PEND */ struct re_guts *re_g; /* none of your business :-) */ } regex_t; typedef struct { regoff_t rm_so; /* start of match */ regoff_t rm_eo; /* end of match */ } regmatch_t; /* regcomp() flags */ # define REG_BASIC 0000 # define REG_EXTENDED 0001 # define REG_ICASE 0002 # define REG_NOSUB 0004 # define REG_NEWLINE 0010 # define REG_NOSPEC 0020 # define REG_PEND 0040 # define REG_DUMP 0200 /* regerror() flags */ # define REG_NOMATCH 1 # define REG_BADPAT 2 # define REG_ECOLLATE 3 # define REG_ECTYPE 4 # define REG_EESCAPE 5 # define REG_ESUBREG 6 # define REG_EBRACK 7 # define REG_EPAREN 8 # define REG_EBRACE 9 # define REG_BADBR 10 # define REG_ERANGE 11 # define REG_ESPACE 12 # define REG_BADRPT 13 # define REG_EMPTY 14 # define REG_ASSERT 15 # define REG_INVARG 16 # define REG_ATOI 255 /* convert name to number (!) */ # define REG_ITOA 0400 /* convert number to name (!) */ /* regexec() flags */ # define REG_NOTBOL 00001 # define REG_NOTEOL 00002 # define REG_STARTEND 00004 # define REG_TRACE 00400 /* tracing of execution */ # define REG_LARGE 01000 /* force large representation */ # define REG_BACKR 02000 /* force use of backref code */ # define regcomp openbsd_regcomp # define regerror openbsd_regerror # define regexec openbsd_regexec # define regfree openbsd_regfree int regcomp(regex_t *, const char *, int); size_t regerror(int, const regex_t *, char *, size_t); int regexec(const regex_t *, const char *, size_t, regmatch_t [], int); void regfree(regex_t *); #endif /* !_REGEX_H_ */ ================================================ FILE: include/bsd_stdlib.h ================================================ /* $OpenBSD: stdlib.h,v 1.67 2016/09/20 21:10:22 fcambus Exp $ */ /* $NetBSD: stdlib.h,v 1.25 1995/12/27 21:19:08 jtc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)stdlib.h 5.13 (Berkeley) 6/4/91 */ #ifndef _COMPAT_STDLIB_H_ # define _COMPAT_STDLIB_H_ # ifdef __solaris__ # undef _TIMESPEC_UTIL_H # define _TIMESPEC_UTIL_H 1 # endif /* ifdef __solaris__ */ # include # include extern char *__progname; const char *bsd_getprogname(void); void *openbsd_reallocarray(void *, size_t, size_t); void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); long long strtonum(const char *, long long, long long, const char **); #endif /* _COMPAT_STDLIB_H_ */ #include_next ================================================ FILE: include/bsd_string.h ================================================ /* $OpenBSD: string.h,v 1.31 2016/09/09 18:12:37 millert Exp $ */ /* $NetBSD: string.h,v 1.6 1994/10/26 00:56:30 cgd Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1990 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)string.h 5.10 (Berkeley) 3/9/91 */ #ifndef _COMPAT_STRING_H_ # define _COMPAT_STRING_H_ size_t openbsd_strlcpy(char *, const char *, size_t); size_t openbsd_strlcat(char *dst, const char *src, size_t dsize); # ifndef __OpenBSD__ # include # endif /* ifndef __OpenBSD__ */ #endif /* _COMPAT_STRING_H_ */ #include_next ================================================ FILE: include/bsd_termios.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1988, 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)termios.h 8.3 (Berkeley) 3/28/94 */ #include_next #ifndef _COMPAT_TERMIOS_H # define _COMPAT_TERMIOS_H # ifndef TCSASOFT # define TCSASOFT 0 # endif /* ifndef TCSASOFT */ # ifndef CCEQ # define CCEQ(val, c) (c == val ? val != _POSIX_VDISABLE : 0) # endif /* ifndef CCEQ */ #endif /* _COMPAT_TERMIOS_H */ ================================================ FILE: include/bsd_unistd.h ================================================ /* $OpenBSD: unistd.h,v 1.103 2016/09/12 19:36:26 guenther Exp $ */ /* $NetBSD: unistd.h,v 1.26.4.1 1996/05/28 02:31:51 mrg Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)unistd.h 5.13 (Berkeley) 6/17/91 */ #ifndef _COMPAT_UNISTD_H_ # define _COMPAT_UNISTD_H_ int openbsd_pledge(const char *, const char *); int openbsd_getopt(int, char * const *, const char *); int openbsd_getopt(int, char * const *, const char *); extern char *openbsd_optarg; extern int openbsd_opterr, openbsd_optind, \ openbsd_optopt, openbsd_optreset; # ifdef __OpenBSD__ # include # else # include # define STDIN_FILENO 0 /* standard input file descriptor */ # define STDOUT_FILENO 1 /* standard output file descriptor */ # define STDERR_FILENO 2 /* standard error file descriptor */ # endif /* _COMPAT_UNISTD_H_ */ #endif /* ifdef __OpenBSD__ */ #define _COMPAT_GETOPT_H_ /* glibc includes getopt.h */ #include_next #undef _COMPAT_GETOPT_H_ ================================================ FILE: include/com_extern.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ int cut(SCR *, CHAR_T *, MARK *, MARK *, int); int cut_line(SCR *, recno_t, size_t, size_t, CB *); void cut_close(GS *); TEXT *text_init(SCR *, const char *, size_t, size_t); void text_lfree(TEXTH *); void text_free(TEXT *); int del(SCR *, MARK *, MARK *, int); FREF *file_add(SCR *, CHAR_T *); int file_init(SCR *, FREF *, char *, int); int file_end(SCR *, EXF *, int); int file_write(SCR *, MARK *, MARK *, char *, int); int file_m1(SCR *, int, int); int file_m2(SCR *, int); int file_m3(SCR *, int); int file_aw(SCR *, int); void set_alt_name(SCR *, char *); lockr_t file_lock(SCR *, char *, int *, int, int); int v_key_init(SCR *); void v_key_ilookup(SCR *); size_t v_key_len(SCR *, CHAR_T); CHAR_T *v_key_name(SCR *, CHAR_T); int v_key_val(SCR *, CHAR_T); int v_event_push(SCR *, EVENT *, CHAR_T *, size_t, unsigned int); int v_event_get(SCR *, EVENT *, int, u_int32_t); void v_event_err(SCR *, EVENT *); int v_event_flush(SCR *, unsigned int); int db_eget(SCR *, recno_t, char **, size_t *, int *); int db_get(SCR *, recno_t, u_int32_t, char **, size_t *); int db_delete(SCR *, recno_t); int db_append(SCR *, int, recno_t, char *, size_t); int db_insert(SCR *, recno_t, char *, size_t); int db_set(SCR *, recno_t, char *, size_t); int db_exist(SCR *, recno_t); int db_last(SCR *, recno_t *); void db_err(SCR *, recno_t); int log_init(SCR *, EXF *); int log_end(SCR *, EXF *); int log_cursor(SCR *); int log_line(SCR *, recno_t, unsigned int); int log_mark(SCR *, LMARK *); int log_backward(SCR *, MARK *); int log_setline(SCR *); int log_forward(SCR *, MARK *); int editor(GS *, int, char *[]); void v_end(GS *); int mark_init(SCR *, EXF *); int mark_end(SCR *, EXF *); int mark_get(SCR *, CHAR_T, MARK *, mtype_t); int mark_set(SCR *, CHAR_T, MARK *, int); int mark_insdel(SCR *, lnop_t, recno_t); void msgq(SCR *, mtype_t, const char *, ...); void msgq_str(SCR *, mtype_t, char *, char *); void mod_rpt(SCR *); void msgq_status(SCR *, recno_t, unsigned int); const char *msg_cmsg(SCR *, cmsg_t, size_t *); char *msg_print(SCR *, const char *, int *); int opts_init(SCR *, int *); int opts_set(SCR *, ARGS *[], char *); int o_set(SCR *, int, unsigned int, char *, unsigned long); int opts_empty(SCR *, int, int); void opts_dump(SCR *, enum optdisp); int opts_save(SCR *, FILE *); OPTLIST const *opts_search(char *); void opts_nomatch(SCR *, char *); int opts_copy(SCR *, SCR *); void opts_free(SCR *); int f_altwerase(SCR *, OPTION *, char *, unsigned long *); int f_columns(SCR *, OPTION *, char *, unsigned long *); int f_lines(SCR *, OPTION *, char *, unsigned long *); int f_paragraph(SCR *, OPTION *, char *, unsigned long *); int f_print(SCR *, OPTION *, char *, unsigned long *); int f_readonly(SCR *, OPTION *, char *, unsigned long *); int f_recompile(SCR *, OPTION *, char *, unsigned long *); int f_reformat(SCR *, OPTION *, char *, unsigned long *); int f_section(SCR *, OPTION *, char *, unsigned long *); int f_secure(SCR *, OPTION *, char *, unsigned long *); int f_ttywerase(SCR *, OPTION *, char *, unsigned long *); int f_w300(SCR *, OPTION *, char *, unsigned long *); int f_w1200(SCR *, OPTION *, char *, unsigned long *); int f_w9600(SCR *, OPTION *, char *, unsigned long *); int f_window(SCR *, OPTION *, char *, unsigned long *); int put(SCR *, CB *, CHAR_T *, MARK *, MARK *, int, int); int rcv_tmp(SCR *, EXF *, char *); int rcv_init(SCR *); int rcv_sync(SCR *, unsigned int); int rcv_list(SCR *); int rcv_read(SCR *, FREF *); int screen_init(GS *, SCR *, SCR **); int screen_end(SCR *); SCR *screen_next(SCR *); int f_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int); int b_search(SCR *, MARK *, MARK *, char *, size_t, char **, unsigned int); void search_busy(SCR *, busy_t); int seq_set(SCR *, CHAR_T *, size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int); int seq_delete(SCR *, CHAR_T *, size_t, seq_t); int seq_mdel(SEQ *); SEQ *seq_find (SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *); void seq_close(GS *); int seq_dump(SCR *, seq_t, int); int seq_save(SCR *, FILE *, char *, seq_t); int e_memcmp(CHAR_T *, EVENT *, size_t); void *binc(SCR *, void *, size_t *, size_t); int nonblank(SCR *, recno_t, size_t *); CHAR_T *v_strdup(SCR *, const CHAR_T *, size_t); enum nresult nget_uslong(unsigned long *, const char *, char **, int); enum nresult nget_slong(long *, const char *, char **, int); void TRACE(SCR *, const char *, ...); ================================================ FILE: include/compat.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _COMPAT_H_ # define _COMPAT_H_ # ifdef __solaris__ # undef _TIMESPEC_UTIL_H # define _TIMESPEC_UTIL_H 1 # endif /* ifdef __solaris__ */ # ifdef _AIX # include # endif /* ifdef _AIX */ # if !defined( _GNU_SOURCE ) # define _GNU_SOURCE # endif /* ifndef _GNU_SOURCE */ # if !defined( DEF_STRONG ) # define DEF_STRONG(x) # endif /* if !defined( DEF_STRONG ) */ # if !defined( PROTO_NORMAL ) # define PROTO_NORMAL(x) # endif /* if !defined( PROTO_NORMAL ) */ # if !defined( DEF_WEAK ) # define DEF_WEAK(x) # endif /* if !defined( DEF_WEAK ) */ /* #define d_namlen d_reclen */ # if !defined D_NAMLEN # if !defined( _DIRENT_HAVE_NAMLEN ) # define D_NAMLEN(x) strnlen(( x )->d_name, ( x )->d_reclen) # else /* if !defined( _DIRENT_HAVE_NAMLEN ) */ # define D_NAMLEN(x) ( x )->d_namlen # endif /* if !defined( _DIRENT_HAVE_NAMLEN ) */ # endif /* if !defined D_NAMLEN */ # ifndef __OpenBSD__ # ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK # define FAIL_INSTEAD_OF_TRYING_FALLBACK # endif /* ifndef FAIL_INSTEAD_OF_TRYING_FALLBACK */ # if !defined( HAVE_ATTRIBUTE__BOUNDED__ ) # define __bounded__(x, y, z) # endif /* if !defined( HAVE_ATTRIBUTE__BOUNDED__ ) */ # ifndef __warn_references # define __warn_references(x, y) # endif /* ifndef __warn_references */ # ifndef __UNUSED # define __UNUSED __attribute__ (( unused )) # endif /* ifndef __UNUSED */ # ifndef __dead # define __dead __attribute__ (( __noreturn__ )) # endif /* ifndef __dead */ # ifndef __BEGIN_DECLS # define __BEGIN_DECLS # endif /* ifndef __BEGIN_DECLS */ # ifndef __END_DECLS # define __END_DECLS # endif /* ifndef __END_DECLS */ # ifndef __BEGIN_HIDDEN_DECLS # define __BEGIN_HIDDEN_DECLS # endif /* ifndef __BEGIN_HIDDEN_DECLS */ # ifndef __END_HIDDEN_DECLS # define __END_HIDDEN_DECLS # endif /* ifndef __END_HIDDEN_DECLS */ # ifndef __CONCAT # define __CONCAT(x, y) x ## y # endif /* ifndef __CONCAT */ # ifndef __STRING # define __STRING(x) #x # endif /* ifndef __STRING */ # ifndef __NetBSD__ # undef __weak_alias # define __weak_alias(new, old) \ extern __typeof ( old ) new __attribute__ (( weak, alias(#old))) # define __strong_alias(new, old) \ extern __typeof ( old ) new __attribute__ (( alias(#old))) # endif /* ifndef __NetBSD__ */ # ifndef SA_LEN # define SA_LEN(X) \ (((struct sockaddr *)( X ))->sa_family == AF_INET \ ? sizeof ( struct sockaddr_in ) \ : ((struct sockaddr *)( X ))->sa_family == AF_INET6 \ ? sizeof ( struct sockaddr_in6 ) \ : sizeof ( struct sockaddr )) # endif /* ifndef SA_LEN */ # ifndef SS_LEN # define SS_LEN(X) \ (((struct sockaddr_storage *)( X ))->ss_family == AF_INET \ ? sizeof ( struct sockaddr_in ) \ : ((struct sockaddr_storage *)( X ))->ss_family == AF_INET6 \ ? sizeof ( struct sockaddr_in6 ) \ : sizeof ( struct sockaddr )) # endif /* ifndef SS_LEN */ # define _PW_BUF_LEN 1024 # define _GR_BUF_LEN 1024 # define NOFILE_MAX NOFILE /* sys/sys/param.h */ /* * File system parameters and macros. * * The file system is made out of blocks of at most MAXBSIZE units, with * smaller units (fragments) only in the last direct block. MAXBSIZE * primarily determines the size of buffers in the buffer pool. It may be * made larger without any effect on existing file systems; however making * it smaller makes some file systems unmountable. */ # define MAXBSIZE ( 64 * 1024 ) # ifndef NL_TEXTMAX # define NL_TEXTMAX 2048 # endif /* ifndef NL_TEXTMAX */ # ifndef howmany # define howmany(n, d) ((( n ) + (( d ) - 1 )) / ( d )) # endif /* ifndef howmany */ /* pwd.h */ # define _PW_NAME_LEN 63 # ifndef S_BLKSIZE # define S_BLKSIZE 512 # endif /* ifndef S_BLKSIZE */ # ifndef MAXNAMLEN # define MAXNAMLEN 255 # endif /* ifndef MAXNAMLEN */ # ifndef UID_MAX # define UID_MAX 60000 # endif /* ifndef UID_MAX */ # ifndef GID_MAX # define GID_MAX 60000 # endif /* ifndef GID_MAX */ /* sys/sys/stat.h */ # ifndef ACCESSPERMS # define ACCESSPERMS \ ( S_IRWXU | S_IRWXG | S_IRWXO ) /* 00777 */ # endif /* ifndef ACCESSPERMS */ # ifndef ALLPERMS # define ALLPERMS \ ( S_ISUID | S_ISGID | S_ISTXT | S_IRWXU | S_IRWXG | S_IRWXO ) /* 00666 */ # endif /* ifndef ALLPERMS */ # ifndef DEFFILEMODE # define DEFFILEMODE \ ( S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ) # endif /* ifndef DEFFILEMODE */ # ifdef S_ISVTX # ifndef S_ISTXT # define S_ISTXT S_ISVTX # endif /* ifndef S_ISVTX */ # endif /* ifdef S_ISVTX */ /* lib/libc/include/thread_private.h */ # define _MUTEX_LOCK(mutex) \ do \ { \ } while ( 0 ) # define _MUTEX_UNLOCK(mutex) \ do \ { \ } while ( 0 ) # define SHA512_Update SHA512Update # define SHA512_CTX SHA2_CTX # define SHA512_Init SHA512Init # define SHA512_Final SHA512Final /* sys/socket.h */ # ifndef RT_TABLEID_MAX # define RT_TABLEID_MAX 255 # endif /* ifndef RT_TABLEID_MAX */ /* sys/syslimits.h */ # ifndef CHILD_MAX # define CHILD_MAX 80 /* max simultaneous processes */ # endif /* ifndef CHILD_MAX */ /* pw_dup.c */ struct passwd *pw_dup(const struct passwd *); int issetugid(void); # ifndef HOST_NAME_MAX # define HOST_NAME_MAX 255 # endif /* ifndef HOST_NAME_MAX */ # ifndef OPEN_MAX # define OPEN_MAX 64 /* max open files per process */ # endif /* ifndef OPEN_MAX */ # endif /* ifndef __OpenBSD__ */ /* pledge */ int openbsd_pledge(const char *, const char *); #endif /* ifndef _COMPAT_H_ */ ================================================ FILE: include/compat_bsd_db.h ================================================ /* $OpenBSD: db.h,v 1.4 2016/05/29 20:47:49 guenther Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 2015 Philip Guenther * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _LIBC_DB_H_ # define _LIBC_DB_H_ /* * Little endian <==> big endian 32-bit swap macros. * M_32_SWAP swap a memory location * P_32_SWAP swap a referenced memory location * P_32_COPY swap from one location to another */ # define M_32_SWAP(a) { \ u_int32_t _tmp = a; \ ((char *)&a)[0] = ((char *)&_tmp)[3]; \ ((char *)&a)[1] = ((char *)&_tmp)[2]; \ ((char *)&a)[2] = ((char *)&_tmp)[1]; \ ((char *)&a)[3] = ((char *)&_tmp)[0]; \ } # define P_32_SWAP(a) { \ u_int32_t _tmp = *(u_int32_t *)a; \ ((char *)a)[0] = ((char *)&_tmp)[3]; \ ((char *)a)[1] = ((char *)&_tmp)[2]; \ ((char *)a)[2] = ((char *)&_tmp)[1]; \ ((char *)a)[3] = ((char *)&_tmp)[0]; \ } # define P_32_COPY(a, b) { \ ((char *)&(b))[0] = ((char *)&(a))[3]; \ ((char *)&(b))[1] = ((char *)&(a))[2]; \ ((char *)&(b))[2] = ((char *)&(a))[1]; \ ((char *)&(b))[3] = ((char *)&(a))[0]; \ } /* * Little endian <==> big endian 16-bit swap macros. * M_16_SWAP swap a memory location * P_16_SWAP swap a referenced memory location * P_16_COPY swap from one location to another */ # define M_16_SWAP(a) { \ u_int16_t _tmp = a; \ ((char *)&a)[0] = ((char *)&_tmp)[1]; \ ((char *)&a)[1] = ((char *)&_tmp)[0]; \ } # define P_16_SWAP(a) { \ u_int16_t _tmp = *(u_int16_t *)a; \ ((char *)a)[0] = ((char *)&_tmp)[1]; \ ((char *)a)[1] = ((char *)&_tmp)[0]; \ } # define P_16_COPY(a, b) { \ ((char *)&(b))[0] = ((char *)&(a))[1]; \ ((char *)&(b))[1] = ((char *)&(a))[0]; \ } __BEGIN_HIDDEN_DECLS DB *__bt_open(const char *, int, int, const BTREEINFO *, int); DB *__hash_open(const char *, int, int, const HASHINFO *, int); DB *__rec_open(const char *, int, int, const RECNOINFO *, int); void __dbpanic(DB *dbp); /* Default hash function, from db/hash/hash_func.c */ u_int32_t __default_hash(const void *, size_t); __END_HIDDEN_DECLS PROTO_NORMAL(dbopen); #endif /* !_LIBC_DB_H_ */ ================================================ FILE: include/ex_extern.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ int ex(SCR **); int ex_cmd(SCR *); int ex_range(SCR *, EXCMD *, int *); int ex_is_abbrev(char *, size_t); int ex_is_unmap(char *, size_t); void ex_badaddr(SCR *, EXCMDLIST const *, enum badaddr, enum nresult); int ex_abbr(SCR *, EXCMD *); int ex_unabbr(SCR *, EXCMD *); int ex_append(SCR *, EXCMD *); int ex_change(SCR *, EXCMD *); int ex_insert(SCR *, EXCMD *); int ex_next(SCR *, EXCMD *); int ex_prev(SCR *, EXCMD *); int ex_rew(SCR *, EXCMD *); int ex_retab(SCR *sp, EXCMD *cmdp); int ex_args(SCR *, EXCMD *); char **ex_buildargv(SCR *, EXCMD *, char *); int argv_init(SCR *, EXCMD *); int argv_exp0(SCR *, EXCMD *, char *, size_t); int argv_exp1(SCR *, EXCMD *, char *, size_t, int); int argv_exp2(SCR *, EXCMD *, char *, size_t); int argv_exp3(SCR *, EXCMD *, char *, size_t); int argv_free(SCR *); int ex_at(SCR *, EXCMD *); int ex_bang(SCR *, EXCMD *); int ex_cd(SCR *, EXCMD *); int ex_delete(SCR *, EXCMD *); int ex_display(SCR *, EXCMD *); int ex_edit(SCR *, EXCMD *); int ex_equal(SCR *, EXCMD *); int ex_file(SCR *, EXCMD *); int ex_filter(SCR *, EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype); int ex_global(SCR *, EXCMD *); int ex_v(SCR *, EXCMD *); int ex_g_insdel(SCR *, lnop_t, recno_t); int ex_screen_copy(SCR *, SCR *); int ex_screen_end(SCR *); int ex_optchange(SCR *, int, char *, unsigned long *); int ex_exrc(SCR *); int ex_run_str(SCR *, char *, char *, size_t, int, int); int ex_join(SCR *, EXCMD *); int ex_map(SCR *, EXCMD *); int ex_unmap(SCR *, EXCMD *); int ex_mark(SCR *, EXCMD *); int ex_mkexrc(SCR *, EXCMD *); int ex_copy(SCR *, EXCMD *); int ex_move(SCR *, EXCMD *); int ex_open(SCR *, EXCMD *); int ex_preserve(SCR *, EXCMD *); int ex_recover(SCR *, EXCMD *); int ex_list(SCR *, EXCMD *); int ex_number(SCR *, EXCMD *); int ex_pr(SCR *, EXCMD *); int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); int ex_ldisplay(SCR *, const char *, size_t, size_t, unsigned int); int ex_scprint(SCR *, MARK *, MARK *); int ex_printf(SCR *, const char *, ...); int ex_puts(SCR *, const char *); int ex_fflush(SCR *sp); int ex_put(SCR *, EXCMD *); int ex_quit(SCR *, EXCMD *); int ex_read(SCR *, EXCMD *); int ex_readfp(SCR *, char *, FILE *, MARK *, recno_t *, int); int ex_bg(SCR *, EXCMD *); int ex_fg(SCR *, EXCMD *); int ex_resize(SCR *, EXCMD *); int ex_sdisplay(SCR *); int ex_script(SCR *, EXCMD *); int sscr_exec(SCR *, recno_t); int sscr_check_input(SCR *); int sscr_input(SCR *); int sscr_end(SCR *); int ex_set(SCR *, EXCMD *); int ex_shell(SCR *, EXCMD *); int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int); int proc_wait(SCR *, pid_t, const char *, int, int); int ex_shiftl(SCR *, EXCMD *); int ex_shiftr(SCR *, EXCMD *); int ex_source(SCR *, EXCMD *); int ex_sourcefd(SCR *, EXCMD *, int); int ex_stop(SCR *, EXCMD *); int ex_s(SCR *, EXCMD *); int ex_subagain(SCR *, EXCMD *); int ex_subtilde(SCR *, EXCMD *); int re_compile(SCR *, char *, size_t, char **, size_t *, regex_t *, unsigned int); void re_error(SCR *, int, regex_t *); int ex_tag_first(SCR *, char *); int ex_tag_push(SCR *, EXCMD *); int ex_tag_next(SCR *, EXCMD *); int ex_tag_prev(SCR *, EXCMD *); int ex_tag_nswitch(SCR *, TAG *, int); int ex_tag_Nswitch(SCR *, TAG *, int); int ex_tag_pop(SCR *, EXCMD *); int ex_tag_top(SCR *, EXCMD *); int ex_tag_display(SCR *); int ex_tag_copy(SCR *, SCR *); int tagq_free(SCR *, TAGQ *); void tag_msg(SCR *, tagmsg_t, char *); int ex_tagf_alloc(SCR *, char *); int ex_tag_free(SCR *); int ex_txt(SCR *, TEXTH *, CHAR_T, u_int32_t); int ex_undo(SCR *, EXCMD *); int ex_help(SCR *, EXCMD *); int ex_usage(SCR *, EXCMD *); int ex_viusage(SCR *, EXCMD *); void ex_cinit(EXCMD *, int, int, recno_t, recno_t, int, ARGS **); void ex_cadd(EXCMD *, ARGS *, char *, size_t); int ex_getline(SCR *, FILE *, size_t *); int ex_ncheck(SCR *, int); int ex_init(SCR *); void ex_emsg(SCR *, char *, exm_t); int ex_version(SCR *, EXCMD *); int ex_visual(SCR *, EXCMD *); int ex_wn(SCR *, EXCMD *); int ex_wq(SCR *, EXCMD *); int ex_write(SCR *, EXCMD *); int ex_xit(SCR *, EXCMD *); int ex_writefp(SCR *, char *, FILE *, MARK *, MARK *, unsigned long *, unsigned long *, int); int ex_yank(SCR *, EXCMD *); int ex_z(SCR *, EXCMD *); ================================================ FILE: include/libgen.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ /* Workarounds for header buglets */ #ifdef __solaris__ # undef _TIMESPEC_UTIL_H # define _TIMESPEC_UTIL_H 1 #endif /* ifdef __solaris__ */ #ifndef _COMPAT_LIBGEN_H_ # define _COMPAT_LIBGEN_H_ char *openbsd_basename(char *); char *openbsd_dirname(char *); # include_next #endif /* _COMPAT_LIBGEN_H_ */ ================================================ FILE: include/mpool.h ================================================ /* $OpenBSD: mpool.h,v 1.1 2015/09/09 15:35:24 guenther Exp $ */ /* $NetBSD: mpool.h,v 1.7 1996/05/03 21:13:41 cgd Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)mpool.h 8.4 (Berkeley) 11/2/95 */ #ifndef _MPOOL_H_ # define _MPOOL_H_ # include /* * The memory pool scheme is a simple one. Each in-memory page is referenced * by a bucket which is threaded in up to two of three ways. All active pages * are threaded on a hash chain (hashed by page number) and an lru chain. * Inactive pages are threaded on a free chain. Each reference to a memory * pool is handed an opaque MPOOL cookie which stores all of this information. */ # define HASHSIZE 128 # define HASHKEY(pgno) ((pgno - 1 + HASHSIZE) % HASHSIZE) /* The BKT structures are the elements of the queues... */ typedef struct _bkt { TAILQ_ENTRY(_bkt) hq; /* hash queue */ TAILQ_ENTRY(_bkt) q; /* lru queue */ void *page; /* page */ pgno_t pgno; /* page number. */ # define MPOOL_DIRTY 0x01 /* page needs to be written */ # define MPOOL_PINNED 0x02 /* page is pinned into memory */ # define MPOOL_INUSE 0x04 /* page address is valid */ u_int8_t flags; /* flags */ } BKT; typedef struct MPOOL { TAILQ_HEAD(_lqh, _bkt) lqh; /* lru queue head */ /* hash queue array */ TAILQ_HEAD(_hqh, _bkt) hqh[HASHSIZE]; /* ... */ pgno_t curcache; /* current number of cached pages */ pgno_t maxcache; /* max number of cached pages */ pgno_t npages; /* number of pages in the file */ unsigned long pagesize; /* file page size */ int fd; /* file descriptor */ /* page in conversion routine */ void (*pgin)(void *, pgno_t, void *); /* ... */ /* page out conversion routine */ void (*pgout)(void *, pgno_t, void *); /* ... */ void *pgcookie; /* cookie for page in/out routines */ # ifdef STATISTICS unsigned long cachehit; unsigned long cachemiss; unsigned long pagealloc; unsigned long pageflush; unsigned long pageget; unsigned long pagenew; unsigned long pageput; unsigned long pageread; unsigned long pagewrite; # endif /* ifdef STATISTICS */ } MPOOL; # define MPOOL_IGNOREPIN 0x01 /* Ignore if the page is pinned. */ # define MPOOL_PAGE_REQUEST 0x01 /* New page w/ specific page number */ # define MPOOL_PAGE_NEXT 0x02 /* New page w/ the next page number */ __BEGIN_HIDDEN_DECLS MPOOL *mpool_open(void *, int, pgno_t, pgno_t); void mpool_filter(MPOOL *, void (*)(void *, pgno_t, void *), void (*)(void *, pgno_t, void *), void *); void *mpool_new(MPOOL *, pgno_t *, unsigned int); void *mpool_get(MPOOL *, pgno_t, unsigned int); int mpool_delete(MPOOL *, void *); int mpool_put(MPOOL *, void *, unsigned int); int mpool_sync(MPOOL *); int mpool_close(MPOOL *); PROTO_NORMAL(mpool_open); PROTO_NORMAL(mpool_filter); PROTO_NORMAL(mpool_new); PROTO_NORMAL(mpool_get); PROTO_NORMAL(mpool_delete); PROTO_NORMAL(mpool_put); PROTO_NORMAL(mpool_sync); PROTO_NORMAL(mpool_close); # ifdef STATISTICS void mpool_stat(MPOOL *); PROTO_NORMAL(mpool_stat); # endif /* ifdef STATISTICS */ __END_HIDDEN_DECLS #endif /* ifndef _MPOOL_H_ */ ================================================ FILE: include/pathnames.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _PATHNAMES_H # define _PATHNAMES_H # ifdef _PATH_EXRC # undef _PATH_EXRC # endif /* ifdef _PATH_EXRC */ # define _PATH_EXRC ".exrc" # ifdef _PATH_NEXRC # undef _PATH_NEXRC # endif /* ifdef _PATH_NEXRC */ # define _PATH_NEXRC ".nexrc" # ifdef _PATH_PRESERVE # undef _PATH_PRESERVE # endif /* ifdef _PATH_PRESERVE */ # define _PATH_PRESERVE "/var/tmp/vi.recover" # ifdef _PATH_SYSEXRC # undef _PATH_TAGS # endif /* ifdef _PATH_SYSEXRC */ # define _PATH_SYSEXRC "/etc/vi.exrc" # ifdef _PATH_TAGS # undef _PATH_TAGS # endif /* ifdef _PATH_TAGS */ # define _PATH_TAGS "tags" # ifndef _PATH_SENDMAIL # define _PATH_SENDMAIL "/usr/sbin/sendmail" # endif /* ifndef _PATH_SENDMAIL */ # ifndef _PATH_STRIP # define _PATH_STRIP "/usr/bin/strip" # endif /* ifndef _PATH_STRIP */ # ifndef _PATH_SYSV_PTY # define _PATH_SYSV_PTY "/dev/ptmx" # endif /* ifndef _PATH_SYSV_PTY */ #endif /* ifndef _PATHNAMES_H */ ================================================ FILE: include/poll.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include_next #ifndef _COMPAT_POLL_H_ # define _COMPAT_POLL_H_ # ifndef INFTIM # define INFTIM (-1) # endif /* ifndef INFTIM */ #endif /* ifndef _COMPAT_POLL_H */ ================================================ FILE: include/sys/proc.h ================================================ /* $OpenBSD: proc.h,v 1.235 2017/02/14 10:31:15 mpi Exp $ */ /* $NetBSD: proc.h,v 1.44 1996/04/22 01:23:21 christos Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1986, 1989, 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1989, 1990, 1991, 1992, 1993 UNIX System Laboratories, Inc. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * 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. * * @(#)proc.h 8.8 (Berkeley) 1/21/94 */ #ifndef _SYS_PROC_H_ # define _SYS_PROC_H_ # include /* * These flags are kept in ps_flags. */ # define PS_CONTROLT 0x00000001 /* Has a controlling terminal. */ # define PS_EXEC 0x00000002 /* Process called exec. */ # define PS_INEXEC 0x00000004 /* Process is doing an exec right now */ # define PS_EXITING 0x00000008 /* Process is exiting. */ # define PS_SUGID 0x00000010 /* Had set id privs since last exec. */ # define PS_SUGIDEXEC 0x00000020 /* last execve() was set[ug]id */ # define PS_PPWAIT 0x00000040 /* Parent waits for exec/exit. */ # define PS_ISPWAIT 0x00000080 /* Is parent of PPWAIT child. */ # define PS_PROFIL 0x00000100 /* Has started profiling. */ # define PS_TRACED 0x00000200 /* Being ptraced. */ # define PS_WAITED 0x00000400 /* Stopped proc was waited for. */ # define PS_COREDUMP 0x00000800 /* Busy coredumping */ # define PS_SINGLEEXIT 0x00001000 /* Other threads must die. */ # define PS_SINGLEUNWIND 0x00002000 /* Other threads must unwind. */ # define PS_NOZOMBIE 0x00004000 /* No signal or zombie at exit. */ # define PS_STOPPED 0x00008000 /* Just stopped, need sig to parent. */ # define PS_SYSTEM 0x00010000 /* No sigs, stats or swapping. */ # define PS_EMBRYO 0x00020000 /* New process, not yet fledged */ # define PS_ZOMBIE 0x00040000 /* Dead and ready to be waited for */ # define PS_NOBROADCASTKILL 0x00080000 /* Process excluded from kill -1. */ # define PS_PLEDGE 0x00100000 /* Has called pledge(2) */ # define PS_WXNEEDED 0x00200000 /* Process may violate W^X */ # define PS_BITS \ ("\20" "\01CONTROLT" "\02EXEC" "\03INEXEC" "\04EXITING" "\05SUGID" \ "\06SUGIDEXEC" "\07PPWAIT" "\010ISPWAIT" "\011PROFIL" "\012TRACED" \ "\013WAITED" "\014COREDUMP" "\015SINGLEEXIT" "\016SINGLEUNWIND" \ "\017NOZOMBIE" "\020STOPPED" "\021SYSTEM" "\022EMBRYO" "\023ZOMBIE" \ "\024NOBROADCASTKILL" "\025PLEDGE" "\026WXNEEDED") /* Status values. */ # define SIDL 1 /* Thread being created by fork. */ # define SRUN 2 /* Currently runnable. */ # define SSLEEP 3 /* Sleeping on an address. */ # define SSTOP 4 /* Debugging or suspension. */ # define SZOMB 5 /* unused */ # define SDEAD 6 /* Thread is almost gone */ # define SONPROC 7 /* Thread is currently on a CPU. */ # define P_ZOMBIE(p) ((p)->p_stat == SDEAD) # define P_HASSIBLING(p) (TAILQ_FIRST(&(p)->p_p->ps_threads) != (p) || \ TAILQ_NEXT((p), p_thr_link) != NULL) /* * These flags are per-thread and kept in p_flag */ # define P_INKTR 0x00000001 /* In a ktrace op, don't recurse */ # define P_PROFPEND 0x00000002 /* SIGPROF needs to be posted */ # define P_ALRMPEND 0x00000004 /* SIGVTALRM needs to be posted */ # define P_SIGSUSPEND 0x00000008 /* Need to restore before-suspend mask */ # define P_CANTSLEEP 0x00000010 /* insomniac thread */ # define P_SELECT 0x00000040 /* Selecting; wakeup/waiting danger. */ # define P_SINTR 0x00000080 /* Sleep is interruptible. */ # define P_SYSTEM 0x00000200 /* No sigs, stats or swapping. */ # define P_TIMEOUT 0x00000400 /* Timing out during sleep. */ # define P_WEXIT 0x00002000 /* Working on exiting. */ # define P_OWEUPC 0x00008000 /* Owe proc an addupc() at next ast. */ # define P_SUSPSINGLE 0x00080000 /* Need to stop for single threading. */ # define P_CONTINUED 0x00800000 /* Proc has continued from a stopped state. */ # define P_THREAD 0x04000000 /* Only a thread, not a real process */ # define P_SUSPSIG 0x08000000 /* Stopped from signal. */ # define P_SOFTDEP 0x10000000 /* Stuck processing softdep worklist */ # define P_CPUPEG 0x40000000 /* Do not move to another cpu. */ # define P_BITS \ ("\20" "\01INKTR" "\02PROFPEND" "\03ALRMPEND" "\04SIGSUSPEND" \ "\05CANTSLEEP" "\07SELECT" "\010SINTR" "\012SYSTEM" "\013TIMEOUT" \ "\016WEXIT" "\020OWEUPC" "\024SUSPSINGLE" "\027XX" \ "\030CONTINUED" "\033THREAD" "\034SUSPSIG" "\035SOFTDEP" "\037CPUPEG") # define THREAD_PID_OFFSET 100000 #endif /* !_SYS_PROC_H_ */ ================================================ FILE: include/sys/queue.h ================================================ /* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 */ #ifndef _SYS_QUEUE_H_ # define _SYS_QUEUE_H_ /* * This file defines five types of data structures: singly-linked lists, * lists, simple queues, tail queues and XOR simple queues. * * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A simple queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are singly * linked to save space, so elements can only be removed from the * head of the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the * list. A simple queue may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * An XOR simple queue is used in the same way as a regular simple queue. * The difference is that the head structure also includes a "cookie" that * is XOR'd with the queue pointer (first, last or next) to generate the * real pointer value. * * For details on the use of these macros, see the queue(3) manual page. */ # if defined(QUEUE_MACRO_DEBUG) || \ (defined(_KERNEL) && defined(DIAGNOSTIC)) # define _Q_INVALIDATE(a) (a) = ((void *)-1) # else # define _Q_INVALIDATE(a) # endif /* if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) */ /* * Singly-linked List definitions. */ # define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } # define SLIST_HEAD_INITIALIZER(head) \ { NULL } # define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List access methods. */ # define SLIST_FIRST(head) ((head)->slh_first) # define SLIST_END(head) NULL # define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) # define SLIST_NEXT(elm, field) ((elm)->field.sle_next) # define SLIST_FOREACH(var, head, field) \ for((var) = SLIST_FIRST(head); \ (var) != SLIST_END(head); \ (var) = SLIST_NEXT(var, field)) # define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST(head); \ (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ (var) = (tvar)) /* * Singly-linked List functions. */ # define SLIST_INIT(head) { \ SLIST_FIRST(head) = SLIST_END(head); \ } # define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ (elm)->field.sle_next = (slistelm)->field.sle_next; \ (slistelm)->field.sle_next = (elm); \ } while (0) # define SLIST_INSERT_HEAD(head, elm, field) do { \ (elm)->field.sle_next = (head)->slh_first; \ (head)->slh_first = (elm); \ } while (0) # define SLIST_REMOVE_AFTER(elm, field) do { \ (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ } while (0) # define SLIST_REMOVE_HEAD(head, field) do { \ (head)->slh_first = (head)->slh_first->field.sle_next; \ } while (0) # define SLIST_REMOVE(head, elm, type, field) do { \ if ((head)->slh_first == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = (head)->slh_first; \ \ while (curelm->field.sle_next != (elm)) \ curelm = curelm->field.sle_next; \ curelm->field.sle_next = \ curelm->field.sle_next->field.sle_next; \ } \ _Q_INVALIDATE((elm)->field.sle_next); \ } while (0) /* * List definitions. */ # define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } # define LIST_HEAD_INITIALIZER(head) \ { NULL } # define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List access methods. */ # define LIST_FIRST(head) ((head)->lh_first) # define LIST_END(head) NULL # define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) # define LIST_NEXT(elm, field) ((elm)->field.le_next) # define LIST_FOREACH(var, head, field) \ for((var) = LIST_FIRST(head); \ (var)!= LIST_END(head); \ (var) = LIST_NEXT(var, field)) # define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST(head); \ (var) && ((tvar) = LIST_NEXT(var, field), 1); \ (var) = (tvar)) /* * List functions. */ # define LIST_INIT(head) do { \ LIST_FIRST(head) = LIST_END(head); \ } while (0) # define LIST_INSERT_AFTER(listelm, elm, field) do { \ if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ (listelm)->field.le_next->field.le_prev = \ &(elm)->field.le_next; \ (listelm)->field.le_next = (elm); \ (elm)->field.le_prev = &(listelm)->field.le_next; \ } while (0) # define LIST_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.le_prev = (listelm)->field.le_prev; \ (elm)->field.le_next = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &(elm)->field.le_next; \ } while (0) # define LIST_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.le_next = (head)->lh_first) != NULL) \ (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ (head)->lh_first = (elm); \ (elm)->field.le_prev = &(head)->lh_first; \ } while (0) # define LIST_REMOVE(elm, field) do { \ if ((elm)->field.le_next != NULL) \ (elm)->field.le_next->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = (elm)->field.le_next; \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) # define LIST_REPLACE(elm, elm2, field) do { \ if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ (elm2)->field.le_next->field.le_prev = \ &(elm2)->field.le_next; \ (elm2)->field.le_prev = (elm)->field.le_prev; \ *(elm2)->field.le_prev = (elm2); \ _Q_INVALIDATE((elm)->field.le_prev); \ _Q_INVALIDATE((elm)->field.le_next); \ } while (0) /* * Tail queue definitions. */ # define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ } # define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } # define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ } /* * Tail queue access methods. */ # define TAILQ_FIRST(head) ((head)->tqh_first) # define TAILQ_END(head) NULL # define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) # define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) # define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) # define TAILQ_EMPTY(head) \ (TAILQ_FIRST(head) == TAILQ_END(head)) # define TAILQ_FOREACH(var, head, field) \ for((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head); \ (var) = TAILQ_NEXT(var, field)) # define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST(head); \ (var) != TAILQ_END(head) && \ ((tvar) = TAILQ_NEXT(var, field), 1); \ (var) = (tvar)) # define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head); \ (var) = TAILQ_PREV(var, headname, field)) # define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST(head, headname); \ (var) != TAILQ_END(head) && \ ((tvar) = TAILQ_PREV(var, headname, field), 1); \ (var) = (tvar)) /* * Tail queue functions. */ # define TAILQ_INIT(head) do { \ (head)->tqh_first = NULL; \ (head)->tqh_last = &(head)->tqh_first; \ } while (0) # define TAILQ_INSERT_HEAD(head, elm, field) do { \ if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ (head)->tqh_first->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (head)->tqh_first = (elm); \ (elm)->field.tqe_prev = &(head)->tqh_first; \ } while (0) # define TAILQ_INSERT_TAIL(head, elm, field) do { \ (elm)->field.tqe_next = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &(elm)->field.tqe_next; \ } while (0) # define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ (elm)->field.tqe_next->field.tqe_prev = \ &(elm)->field.tqe_next; \ else \ (head)->tqh_last = &(elm)->field.tqe_next; \ (listelm)->field.tqe_next = (elm); \ (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ } while (0) # define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ (elm)->field.tqe_next = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ } while (0) # define TAILQ_REMOVE(head, elm, field) do { \ if (((elm)->field.tqe_next) != NULL) \ (elm)->field.tqe_next->field.tqe_prev = \ (elm)->field.tqe_prev; \ else \ (head)->tqh_last = (elm)->field.tqe_prev; \ *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) # define TAILQ_REPLACE(head, elm, elm2, field) do { \ if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ (elm2)->field.tqe_next->field.tqe_prev = \ &(elm2)->field.tqe_next; \ else \ (head)->tqh_last = &(elm2)->field.tqe_next; \ (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ *(elm2)->field.tqe_prev = (elm2); \ _Q_INVALIDATE((elm)->field.tqe_prev); \ _Q_INVALIDATE((elm)->field.tqe_next); \ } while (0) # define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ } \ } while (0) #endif /* !_SYS_QUEUE_H_ */ ================================================ FILE: include/sys/stat.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _COMPAT_SYS_STAT_H_ # define _COMPAT_SYS_STAT_H_ # ifndef S_ISTXT # define S_ISTXT S_ISVTX /* sticky bit */ # endif /* ifndef S_ISTXT */ # ifndef st_atimespec # define st_atimespec st_atim # endif /* ifndef st_atimespec */ # ifndef st_atimensec # define st_atimensec st_atim.tv_nsec # endif /* ifndef st_atimensec */ # ifndef st_mtimespec # define st_mtimespec st_mtim # endif /* ifndef st_mtimespec */ # ifndef st_mtimensec # define st_mtimensec st_mtim.tv_nsec # endif /* ifndef st_mtimensec */ # ifndef st_ctimespec # define st_ctimespec st_ctim # endif /* ifndef st_ctimespec */ # ifndef st_ctimensec # define st_ctimensec st_ctim.tv_nsec # endif /* ifndef st_ctimensec */ #endif /* !_COMPAT_SYS_STAT_H_ */ #include_next ================================================ FILE: include/sys/time.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include_next #ifndef _COMPAT_SYS_TIME_H_ # define _COMPAT_SYS_TIME_H_ # include # ifndef TIMEVAL_TO_TIMESPEC # define TIMEVAL_TO_TIMESPEC(tv, ts) { \ (ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * 1000; \ } # endif /* ifndef TIMEVAL_TO_TIMESPEC */ # ifndef TIMESPEC_TO_TIMEVAL # define TIMESPEC_TO_TIMEVAL(tv, ts) { \ (tv)->tv_sec = (ts)->tv_sec; \ (tv)->tv_usec = (ts)->tv_nsec / 1000; \ } # endif /* ifndef TIMESPEC_TO_TIMEVAL */ # ifdef timespecclear # undef timespecclear # endif /* ifdef timespecclear */ # define timespecclear(tsp) (tsp)->tv_sec = (tsp)->tv_nsec = 0 # ifdef timespecisset # undef timespecisset # endif /* ifdef timespecisset */ # define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) # ifdef timespeccmp # undef timespeccmp # endif /* ifdef timespeccmp */ # define timespeccmp(tsp, usp, cmp) \ (((tsp)->tv_sec == (usp)->tv_sec) ? \ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ ((tsp)->tv_sec cmp (usp)->tv_sec)) # ifdef timespecadd # undef timespecadd # endif /* ifdef timespecadd */ # define timespecadd(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ if ((vsp)->tv_nsec >= 1000000000L) { \ (vsp)->tv_sec++; \ (vsp)->tv_nsec -= 1000000000L; \ } \ } while (0) # ifdef timespecsub # undef timespecsub # endif /* ifdef timespecsub */ # define timespecsub(tsp, usp, vsp) \ do { \ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ if ((vsp)->tv_nsec < 0) { \ (vsp)->tv_sec--; \ (vsp)->tv_nsec += 1000000000L; \ } \ } while (0) #endif /* _COMPAT_SYS_TIME_H_ */ ================================================ FILE: include/sys/tree.h ================================================ /* $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2002 Niels Provos * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. */ #ifndef _SYS_TREE_H_ # define _SYS_TREE_H_ /* * This file defines data structures for different types of trees: * splay trees and red-black trees. * * A splay tree is a self-organizing data structure. Every operation * on the tree causes a splay to happen. The splay moves the requested * node to the root of the tree and partly rebalances it. * * This has the benefit that request locality causes faster lookups as * the requested nodes move to the top of the tree. On the other hand, * every lookup causes memory writes. * * The Balance Theorem bounds the total access time for m operations * and n inserts on an initially empty tree as O((m + n)lg n). The * amortized cost for a sequence of m accesses to a splay tree is O(lg n); * * A red-black tree is a binary search tree with the node color as an * extra attribute. It fulfills a set of conditions: * * - every search path from the root to a leaf consists of the * same number of black nodes, * - each red node (except for the root) has a black parent, * - each leaf node is black. * * Every operation on a red-black tree is bounded as O(lg n). * The maximum height of a red-black tree is 2lg (n+1). */ # define SPLAY_HEAD(name, type) \ struct name { \ struct type *sph_root; /* root of the tree */ \ } # define SPLAY_INITIALIZER(root) \ { NULL } # define SPLAY_INIT(root) do { \ (root)->sph_root = NULL; \ } while (0) # define SPLAY_ENTRY(type) \ struct { \ struct type *spe_left; /* left element */ \ struct type *spe_right; /* right element */ \ } # define SPLAY_LEFT(elm, field) (elm)->field.spe_left # define SPLAY_RIGHT(elm, field) (elm)->field.spe_right # define SPLAY_ROOT(head) (head)->sph_root # define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) /* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ # define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) # define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ (head)->sph_root = tmp; \ } while (0) # define SPLAY_LINKLEFT(head, tmp, field) do { \ SPLAY_LEFT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ } while (0) # define SPLAY_LINKRIGHT(head, tmp, field) do { \ SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ tmp = (head)->sph_root; \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ } while (0) # define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ } while (0) /* Generates prototypes and inline functions */ # define SPLAY_PROTOTYPE(name, type, field, cmp) \ void name##_SPLAY(struct name *, struct type *); \ void name##_SPLAY_MINMAX(struct name *, int); \ struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ \ /* Finds the node with the same key as elm */ \ static __inline struct type * \ name##_SPLAY_FIND(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) \ return(NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) \ return (head->sph_root); \ return (NULL); \ } \ \ static __inline struct type * \ name##_SPLAY_NEXT(struct name *head, struct type *elm) \ { \ name##_SPLAY(head, elm); \ if (SPLAY_RIGHT(elm, field) != NULL) { \ elm = SPLAY_RIGHT(elm, field); \ while (SPLAY_LEFT(elm, field) != NULL) { \ elm = SPLAY_LEFT(elm, field); \ } \ } else \ elm = NULL; \ return (elm); \ } \ \ static __inline struct type * \ name##_SPLAY_MIN_MAX(struct name *head, int val) \ { \ name##_SPLAY_MINMAX(head, val); \ return (SPLAY_ROOT(head)); \ } /* Main splay operation. * Moves node close to the key of elm to top */ # define SPLAY_GENERATE(name, type, field, cmp) \ struct type * \ name##_SPLAY_INSERT(struct name *head, struct type *elm) \ { \ if (SPLAY_EMPTY(head)) { \ SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ } else { \ int __comp; \ name##_SPLAY(head, elm); \ __comp = (cmp)(elm, (head)->sph_root); \ if(__comp < 0) { \ SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ SPLAY_RIGHT(elm, field) = (head)->sph_root; \ SPLAY_LEFT((head)->sph_root, field) = NULL; \ } else if (__comp > 0) { \ SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ SPLAY_LEFT(elm, field) = (head)->sph_root; \ SPLAY_RIGHT((head)->sph_root, field) = NULL; \ } else \ return ((head)->sph_root); \ } \ (head)->sph_root = (elm); \ return (NULL); \ } \ \ struct type * \ name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ { \ struct type *__tmp; \ if (SPLAY_EMPTY(head)) \ return (NULL); \ name##_SPLAY(head, elm); \ if ((cmp)(elm, (head)->sph_root) == 0) { \ if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ } else { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ name##_SPLAY(head, elm); \ SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ } \ return (elm); \ } \ return (NULL); \ } \ \ void \ name##_SPLAY(struct name *head, struct type *elm) \ { \ struct type __node, *__left, *__right, *__tmp; \ int __comp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while ((__comp = (cmp)(elm, (head)->sph_root))) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if ((cmp)(elm, __tmp) > 0){ \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } \ \ /* Splay with either the minimum or the maximum element \ * Used to find minimum or maximum element in tree. \ */ \ void name##_SPLAY_MINMAX(struct name *head, int __comp) \ { \ struct type __node, *__left, *__right, *__tmp; \ \ SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ __left = __right = &__node; \ \ while (1) { \ if (__comp < 0) { \ __tmp = SPLAY_LEFT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp < 0){ \ SPLAY_ROTATE_RIGHT(head, __tmp, field); \ if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKLEFT(head, __right, field); \ } else if (__comp > 0) { \ __tmp = SPLAY_RIGHT((head)->sph_root, field); \ if (__tmp == NULL) \ break; \ if (__comp > 0) { \ SPLAY_ROTATE_LEFT(head, __tmp, field); \ if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ break; \ } \ SPLAY_LINKRIGHT(head, __left, field); \ } \ } \ SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ } # define SPLAY_NEGINF -1 # define SPLAY_INF 1 # define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) # define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) # define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) # define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) # define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) # define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) # define SPLAY_FOREACH(x, name, head) \ for ((x) = SPLAY_MIN(name, head); \ (x) != NULL; \ (x) = SPLAY_NEXT(name, head, x)) /* Macros that define a red-black tree */ # define RB_HEAD(name, type) \ struct name { \ struct type *rbh_root; /* root of the tree */ \ } # define RB_INITIALIZER(root) \ { NULL } # define RB_INIT(root) do { \ (root)->rbh_root = NULL; \ } while (0) # define RB_BLACK 0 # define RB_RED 1 # define RB_ENTRY(type) \ struct { \ struct type *rbe_left; /* left element */ \ struct type *rbe_right; /* right element */ \ struct type *rbe_parent; /* parent element */ \ int rbe_color; /* node color */ \ } # define RB_LEFT(elm, field) (elm)->field.rbe_left # define RB_RIGHT(elm, field) (elm)->field.rbe_right # define RB_PARENT(elm, field) (elm)->field.rbe_parent # define RB_COLOR(elm, field) (elm)->field.rbe_color # define RB_ROOT(head) (head)->rbh_root # define RB_EMPTY(head) (RB_ROOT(head) == NULL) # define RB_SET(elm, parent, field) do { \ RB_PARENT(elm, field) = parent; \ RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ RB_COLOR(elm, field) = RB_RED; \ } while (0) # define RB_SET_BLACKRED(black, red, field) do { \ RB_COLOR(black, field) = RB_BLACK; \ RB_COLOR(red, field) = RB_RED; \ } while (0) # ifndef RB_AUGMENT # define RB_AUGMENT(x) do {} while (0) # endif /* ifndef RB_AUGMENT */ # define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ (tmp) = RB_RIGHT(elm, field); \ if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_LEFT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) # define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ (tmp) = RB_LEFT(elm, field); \ if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ } \ RB_AUGMENT(elm); \ if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ else \ RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ } else \ (head)->rbh_root = (tmp); \ RB_RIGHT(tmp, field) = (elm); \ RB_PARENT(elm, field) = (tmp); \ RB_AUGMENT(tmp); \ if ((RB_PARENT(tmp, field))) \ RB_AUGMENT(RB_PARENT(tmp, field)); \ } while (0) /* Generates prototypes and inline functions */ # define RB_PROTOTYPE(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) # define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) # define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ attr struct type *name##_RB_INSERT(struct name *, struct type *); \ attr struct type *name##_RB_FIND(struct name *, struct type *); \ attr struct type *name##_RB_NFIND(struct name *, struct type *); \ attr struct type *name##_RB_NEXT(struct type *); \ attr struct type *name##_RB_PREV(struct type *); \ attr struct type *name##_RB_MINMAX(struct name *, int); \ \ /* Main rb operation. * Moves node close to the key of elm to top */ # define RB_GENERATE(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp,) # define RB_GENERATE_STATIC(name, type, field, cmp) \ RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) # define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ attr void \ name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ { \ struct type *parent, *gparent, *tmp; \ while ((parent = RB_PARENT(elm, field)) && \ RB_COLOR(parent, field) == RB_RED) { \ gparent = RB_PARENT(parent, field); \ if (parent == RB_LEFT(gparent, field)) { \ tmp = RB_RIGHT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_RIGHT(parent, field) == elm) { \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_RIGHT(head, gparent, tmp, field); \ } else { \ tmp = RB_LEFT(gparent, field); \ if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ RB_COLOR(tmp, field) = RB_BLACK; \ RB_SET_BLACKRED(parent, gparent, field);\ elm = gparent; \ continue; \ } \ if (RB_LEFT(parent, field) == elm) { \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = parent; \ parent = elm; \ elm = tmp; \ } \ RB_SET_BLACKRED(parent, gparent, field); \ RB_ROTATE_LEFT(head, gparent, tmp, field); \ } \ } \ RB_COLOR(head->rbh_root, field) = RB_BLACK; \ } \ \ attr void \ name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ { \ struct type *tmp; \ while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ elm != RB_ROOT(head)) { \ if (RB_LEFT(parent, field) == elm) { \ tmp = RB_RIGHT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_LEFT(head, parent, tmp, field);\ tmp = RB_RIGHT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ struct type *oleft; \ if ((oleft = RB_LEFT(tmp, field)))\ RB_COLOR(oleft, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_RIGHT(head, tmp, oleft, field);\ tmp = RB_RIGHT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_RIGHT(tmp, field)) \ RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_LEFT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } else { \ tmp = RB_LEFT(parent, field); \ if (RB_COLOR(tmp, field) == RB_RED) { \ RB_SET_BLACKRED(tmp, parent, field); \ RB_ROTATE_RIGHT(head, parent, tmp, field);\ tmp = RB_LEFT(parent, field); \ } \ if ((RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ (RB_RIGHT(tmp, field) == NULL || \ RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ RB_COLOR(tmp, field) = RB_RED; \ elm = parent; \ parent = RB_PARENT(elm, field); \ } else { \ if (RB_LEFT(tmp, field) == NULL || \ RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ struct type *oright; \ if ((oright = RB_RIGHT(tmp, field)))\ RB_COLOR(oright, field) = RB_BLACK;\ RB_COLOR(tmp, field) = RB_RED; \ RB_ROTATE_LEFT(head, tmp, oright, field);\ tmp = RB_LEFT(parent, field); \ } \ RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ RB_COLOR(parent, field) = RB_BLACK; \ if (RB_LEFT(tmp, field)) \ RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ RB_ROTATE_RIGHT(head, parent, tmp, field);\ elm = RB_ROOT(head); \ break; \ } \ } \ } \ if (elm) \ RB_COLOR(elm, field) = RB_BLACK; \ } \ \ attr struct type * \ name##_RB_REMOVE(struct name *head, struct type *elm) \ { \ struct type *child, *parent, *old = elm; \ int color; \ if (RB_LEFT(elm, field) == NULL) \ child = RB_RIGHT(elm, field); \ else if (RB_RIGHT(elm, field) == NULL) \ child = RB_LEFT(elm, field); \ else { \ struct type *left; \ elm = RB_RIGHT(elm, field); \ while ((left = RB_LEFT(elm, field))) \ elm = left; \ child = RB_RIGHT(elm, field); \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ if (RB_PARENT(elm, field) == old) \ parent = elm; \ (elm)->field = (old)->field; \ if (RB_PARENT(old, field)) { \ if (RB_LEFT(RB_PARENT(old, field), field) == old)\ RB_LEFT(RB_PARENT(old, field), field) = elm;\ else \ RB_RIGHT(RB_PARENT(old, field), field) = elm;\ RB_AUGMENT(RB_PARENT(old, field)); \ } else \ RB_ROOT(head) = elm; \ RB_PARENT(RB_LEFT(old, field), field) = elm; \ if (RB_RIGHT(old, field)) \ RB_PARENT(RB_RIGHT(old, field), field) = elm; \ if (parent) { \ left = parent; \ do { \ RB_AUGMENT(left); \ } while ((left = RB_PARENT(left, field))); \ } \ goto color; \ } \ parent = RB_PARENT(elm, field); \ color = RB_COLOR(elm, field); \ if (child) \ RB_PARENT(child, field) = parent; \ if (parent) { \ if (RB_LEFT(parent, field) == elm) \ RB_LEFT(parent, field) = child; \ else \ RB_RIGHT(parent, field) = child; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = child; \ color: \ if (color == RB_BLACK) \ name##_RB_REMOVE_COLOR(head, parent, child); \ return (old); \ } \ \ /* Inserts a node into the RB tree */ \ attr struct type * \ name##_RB_INSERT(struct name *head, struct type *elm) \ { \ struct type *tmp; \ struct type *parent = NULL; \ int comp = 0; \ tmp = RB_ROOT(head); \ while (tmp) { \ parent = tmp; \ comp = (cmp)(elm, parent); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ RB_SET(elm, parent, field); \ if (parent != NULL) { \ if (comp < 0) \ RB_LEFT(parent, field) = elm; \ else \ RB_RIGHT(parent, field) = elm; \ RB_AUGMENT(parent); \ } else \ RB_ROOT(head) = elm; \ name##_RB_INSERT_COLOR(head, elm); \ return (NULL); \ } \ \ /* Finds the node with the same key as elm */ \ attr struct type * \ name##_RB_FIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) \ tmp = RB_LEFT(tmp, field); \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (NULL); \ } \ \ /* Finds the first node greater than or equal to the search key */ \ attr struct type * \ name##_RB_NFIND(struct name *head, struct type *elm) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *res = NULL; \ int comp; \ while (tmp) { \ comp = cmp(elm, tmp); \ if (comp < 0) { \ res = tmp; \ tmp = RB_LEFT(tmp, field); \ } \ else if (comp > 0) \ tmp = RB_RIGHT(tmp, field); \ else \ return (tmp); \ } \ return (res); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_NEXT(struct type *elm) \ { \ if (RB_RIGHT(elm, field)) { \ elm = RB_RIGHT(elm, field); \ while (RB_LEFT(elm, field)) \ elm = RB_LEFT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ /* ARGSUSED */ \ attr struct type * \ name##_RB_PREV(struct type *elm) \ { \ if (RB_LEFT(elm, field)) { \ elm = RB_LEFT(elm, field); \ while (RB_RIGHT(elm, field)) \ elm = RB_RIGHT(elm, field); \ } else { \ if (RB_PARENT(elm, field) && \ (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ elm = RB_PARENT(elm, field); \ else { \ while (RB_PARENT(elm, field) && \ (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ elm = RB_PARENT(elm, field); \ elm = RB_PARENT(elm, field); \ } \ } \ return (elm); \ } \ \ attr struct type * \ name##_RB_MINMAX(struct name *head, int val) \ { \ struct type *tmp = RB_ROOT(head); \ struct type *parent = NULL; \ while (tmp) { \ parent = tmp; \ if (val < 0) \ tmp = RB_LEFT(tmp, field); \ else \ tmp = RB_RIGHT(tmp, field); \ } \ return (parent); \ } # define RB_NEGINF -1 # define RB_INF 1 # define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) # define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) # define RB_FIND(name, x, y) name##_RB_FIND(x, y) # define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) # define RB_NEXT(name, x, y) name##_RB_NEXT(y) # define RB_PREV(name, x, y) name##_RB_PREV(y) # define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) # define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) # define RB_FOREACH(x, name, head) \ for ((x) = RB_MIN(name, head); \ (x) != NULL; \ (x) = name##_RB_NEXT(x)) # define RB_FOREACH_SAFE(x, name, head, y) \ for ((x) = RB_MIN(name, head); \ ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \ (x) = (y)) # define RB_FOREACH_REVERSE(x, name, head) \ for ((x) = RB_MAX(name, head); \ (x) != NULL; \ (x) = name##_RB_PREV(x)) # define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ for ((x) = RB_MAX(name, head); \ ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \ (x) = (y)) #endif /* _SYS_TREE_H_ */ ================================================ FILE: include/sys/types.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _COMPAT_SYS_TYPES_H_ # define _COMPAT_SYS_TYPES_H_ /* * for major() / minor() macros with glibc, needs to be included * before */ # if defined(__GNU_LIBRARY__) && defined(__GLIBC_PREREQ) # include # endif /* if defined(__GNU_LIBRARY__) && defined(__GLIBC_PREREQ) */ #endif /* _COMPAT_SYS_TYPES_H_ */ #include_next ================================================ FILE: include/util.h ================================================ /* $OpenBSD: util.h,v 1.5 2022/12/26 19:16:03 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)util.h 10.5 (Berkeley) 3/16/96 */ #ifndef _UTIL_H # define _UTIL_H /* Macros to init/set/clear/test flags. */ # define FL_INIT(l, f) (l) = (f) /* Specific flags location. */ # define FL_SET(l, f) ((l) |= (f)) # define FL_CLR(l, f) ((l) &= ~(f)) # define FL_ISSET(l, f) ((l) & (f)) # define LF_INIT(f) FL_INIT(flags, (f)) /* Local variable flags. */ # define LF_SET(f) FL_SET(flags, (f)) # define LF_CLR(f) FL_CLR(flags, (f)) # define LF_ISSET(f) FL_ISSET(flags, (f)) # define F_INIT(p, f) FL_INIT((p)->flags, (f)) /* Structure element flags. */ # define F_SET(p, f) FL_SET((p)->flags, (f)) # define F_CLR(p, f) FL_CLR((p)->flags, (f)) # define F_ISSET(p, f) FL_ISSET((p)->flags, (f)) /* Offset to next column of stop size, e.g. tab offsets. */ # define COL_OFF(c, stop) ((stop) - ((c) % (stop))) /* Busy message types. */ typedef enum { B_NONE, B_OFF, B_READ, B_RECOVER, B_SEARCH, B_WRITE } bmsg_t; /* * Number handling defines and prototypes. * * NNFITS: test for addition of two negative numbers under a limit * NPFITS: test for addition of two positive numbers under a limit * NADD_SLONG: test for addition of two signed longs * NADD_USLONG: test for addition of two unsigned longs */ enum nresult { NUM_ERR, NUM_OK, NUM_OVER, NUM_UNDER }; # define NNFITS(min, cur, add) \ (((long)(min)) - (cur) <= (add)) # define NPFITS(max, cur, add) \ (((unsigned long)(max)) - (cur) >= (add)) # define NADD_SLONG(v1, v2) \ ((v1) < 0 ? \ ((v2) < 0 && \ NNFITS(LONG_MIN, (v1), (v2))) ? NUM_UNDER : NUM_OK : \ (v1) > 0 ? \ (v2) > 0 && \ NPFITS(LONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER : \ NUM_OK) #endif /* ifndef _UTIL_H */ ================================================ FILE: include/vi_extern.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ int cs_init(SCR *, VCS *); int cs_next(SCR *, VCS *); int cs_fspace(SCR *, VCS *); int cs_fblank(SCR *, VCS *); int cs_prev(SCR *, VCS *); int cs_bblank(SCR *, VCS *); int v_at(SCR *, VICMD *); int v_chrepeat(SCR *, VICMD *); int v_chrrepeat(SCR *, VICMD *); int v_cht(SCR *, VICMD *); int v_chf(SCR *, VICMD *); int v_chT(SCR *, VICMD *); int v_chF(SCR *, VICMD *); int v_delete(SCR *, VICMD *); int v_again(SCR *, VICMD *); int v_exmode(SCR *, VICMD *); int v_join(SCR *, VICMD *); int v_shiftl(SCR *, VICMD *); int v_shiftr(SCR *, VICMD *); int v_suspend(SCR *, VICMD *); int v_switch(SCR *, VICMD *); int v_tagpush(SCR *, VICMD *); int v_tagpop(SCR *, VICMD *); int v_filter(SCR *, VICMD *); int v_event_exec(SCR *, VICMD *); int v_ex(SCR *, VICMD *); int v_ecl_exec(SCR *); int v_increment(SCR *, VICMD *); int v_screen_copy(SCR *, SCR *); int v_screen_end(SCR *); int v_optchange(SCR *, int, char *, unsigned long *); int v_iA(SCR *, VICMD *); int v_ia(SCR *, VICMD *); int v_iI(SCR *, VICMD *); int v_ii(SCR *, VICMD *); int v_iO(SCR *, VICMD *); int v_io(SCR *, VICMD *); int v_change(SCR *, VICMD *); int v_Replace(SCR *, VICMD *); int v_subst(SCR *, VICMD *); int v_left(SCR *, VICMD *); int v_cfirst(SCR *, VICMD *); int v_first(SCR *, VICMD *); int v_ncol(SCR *, VICMD *); int v_zero(SCR *, VICMD *); int v_mark(SCR *, VICMD *); int v_bmark(SCR *, VICMD *); int v_fmark(SCR *, VICMD *); int v_match(SCR *, VICMD *); int v_paragraphf(SCR *, VICMD *); int v_paragraphb(SCR *, VICMD *); int v_buildps(SCR *, char *, char *); int v_Put(SCR *, VICMD *); int v_put(SCR *, VICMD *); int v_redraw(SCR *, VICMD *); int v_replace(SCR *, VICMD *); int v_right(SCR *, VICMD *); int v_dollar(SCR *, VICMD *); int v_screen(SCR *, VICMD *); int v_lgoto(SCR *, VICMD *); int v_home(SCR *, VICMD *); int v_middle(SCR *, VICMD *); int v_bottom(SCR *, VICMD *); int v_up(SCR *, VICMD *); int v_cr(SCR *, VICMD *); int v_down(SCR *, VICMD *); int v_hpageup(SCR *, VICMD *); int v_hpagedown(SCR *, VICMD *); int v_pagedown(SCR *, VICMD *); int v_pageup(SCR *, VICMD *); int v_lineup(SCR *, VICMD *); int v_linedown(SCR *, VICMD *); int v_searchb(SCR *, VICMD *); int v_searchf(SCR *, VICMD *); int v_searchN(SCR *, VICMD *); int v_searchn(SCR *, VICMD *); int v_searchw(SCR *, VICMD *); int v_correct(SCR *, VICMD *, int); int v_sectionf(SCR *, VICMD *); int v_sectionb(SCR *, VICMD *); int v_sentencef(SCR *, VICMD *); int v_sentenceb(SCR *, VICMD *); int v_status(SCR *, VICMD *); int v_tcmd(SCR *, VICMD *, CHAR_T, unsigned int); int v_txt(SCR *, VICMD *, MARK *, const char *, size_t, CHAR_T, recno_t, unsigned long, u_int32_t); int v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *); int v_ulcase(SCR *, VICMD *); int v_mulcase(SCR *, VICMD *); int v_Undo(SCR *, VICMD *); int v_undo(SCR *, VICMD *); void v_eof(SCR *, MARK *); void v_eol(SCR *, MARK *); void v_nomove(SCR *); void v_sof(SCR *, MARK *); void v_sol(SCR *); int v_isempty(char *, size_t); void v_emsg(SCR *, char *, vim_t); int v_wordW(SCR *, VICMD *); int v_wordw(SCR *, VICMD *); int v_wordE(SCR *, VICMD *); int v_worde(SCR *, VICMD *); int v_wordB(SCR *, VICMD *); int v_wordb(SCR *, VICMD *); int v_xchar(SCR *, VICMD *); int v_Xchar(SCR *, VICMD *); int v_yank(SCR *, VICMD *); int v_z(SCR *, VICMD *); int vs_crel(SCR *, long); int v_zexit(SCR *, VICMD *); int vi(SCR **); int vs_line(SCR *, SMAP *, size_t *, size_t *); int vs_number(SCR *); void vs_busy(SCR *, const char *, busy_t); void vs_home(SCR *); void vs_update(SCR *, const char *, const char *); void vs_msg(SCR *, mtype_t, char *, size_t); int vs_ex_resolve(SCR *, int *); int vs_resolve(SCR *, SCR *, int); int vs_repaint(SCR *, EVENT *); int vs_refresh(SCR *, int); int vs_column(SCR *, size_t *); size_t vs_screens(SCR *, recno_t, size_t *); size_t vs_columns(SCR *, char *, recno_t, size_t *, size_t *); size_t vs_rcm(SCR *, recno_t, int); size_t vs_colpos(SCR *, recno_t, size_t); int vs_change(SCR *, recno_t, lnop_t); int vs_sm_fill(SCR *, recno_t, pos_t); int vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t); int vs_sm_1up(SCR *); int vs_sm_1down(SCR *); int vs_sm_next(SCR *, SMAP *, SMAP *); int vs_sm_prev(SCR *, SMAP *, SMAP *); int vs_sm_cursor(SCR *, SMAP **); int vs_sm_position(SCR *, MARK *, unsigned long, pos_t); recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t); int vs_split(SCR *, SCR *, int); int vs_discard(SCR *, SCR **); int vs_fg(SCR *, SCR **, CHAR_T *, int); int vs_bg(SCR *); int vs_swap(SCR *, SCR **, char *); int vs_resize(SCR *, long, adj_t); ================================================ FILE: openbsd/basename.c ================================================ /* $OpenBSD: basename.c,v 1.17 2020/10/20 19:30:14 naddy Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 1997, 2004 Todd C. Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #undef open char * openbsd_basename(char *path) { static char bname[PATH_MAX]; size_t len; const char *endp, *startp; /* Empty or NULL string gets treated as "." */ if (path == NULL || *path == '\0') { bname[0] = '.'; bname[1] = '\0'; return bname; } /* Strip any trailing slashes */ endp = path + strlen(path) - 1; while (endp > path && *endp == '/') { endp--; } /* All slashes becomes "/" */ if (endp == path && *endp == '/') { bname[0] = '/'; bname[1] = '\0'; return bname; } /* Find the start of the base */ startp = endp; while (startp > path && *( startp - 1 ) != '/') { startp--; } len = endp - startp + 1; if (len >= sizeof ( bname )) { errno = ENAMETOOLONG; return NULL; } memcpy(bname, startp, len); bname[len] = '\0'; return bname; } ================================================ FILE: openbsd/dirname.c ================================================ /* $OpenBSD: dirname.c,v 1.17 2020/10/20 19:30:14 naddy Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 1997, 2004 Todd C. Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #undef open char * openbsd_dirname(char *path) { static char dname[PATH_MAX]; size_t len; const char *endp; /* Empty or NULL string gets treated as "." */ if (path == NULL || *path == '\0') { dname[0] = '.'; dname[1] = '\0'; return dname; } /* Strip any trailing slashes */ endp = path + strlen(path) - 1; while (endp > path && *endp == '/') { endp--; } /* Find the start of the dir */ while (endp > path && *endp != '/') { endp--; } /* Either the dir is "/" or there are no slashes */ if (endp == path) { dname[0] = *endp == '/' ? '/' : '.'; dname[1] = '\0'; return dname; } else { /* Move forward past the separating slashes */ do { endp--; } while ( endp > path && *endp == '/' ); } len = endp - path + 1; if (len >= sizeof ( dname )) { errno = ENAMETOOLONG; return NULL; } memcpy(dname, path, len); dname[len] = '\0'; return dname; } ================================================ FILE: openbsd/err.c ================================================ /* $OpenBSD: err.c,v 1.12 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #undef open void openbsd_err(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_verr(eval, fmt, ap); va_end(ap); } ================================================ FILE: openbsd/errc.c ================================================ /* $OpenBSD: errc.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include #include #include "errc.h" #undef open void openbsd_errc(int eval, int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_verrc(eval, code, fmt, ap); va_end(ap); } ================================================ FILE: openbsd/errc.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _ERRC_H # define _ERRC_H # include "../include/compat.h" # ifndef __solaris__ # include # include # else # undef _TIMESPEC_UTIL_H # define _TIMESPEC_UTIL_H 1 # endif /* ifndef __solaris__ */ # include # include # include # undef open void openbsd_errc(int eval, int code, const char *fmt, ...); void openbsd_err(int eval, const char *fmt, ...); void openbsd_errx(int eval, const char *fmt, ...); void openbsd_verrc(int eval, int code, const char *fmt, va_list ap); void openbsd_verr(int eval, const char *fmt, va_list ap); void openbsd_verrx(int eval, const char *fmt, va_list ap); void openbsd_vwarnc(int code, const char *fmt, va_list ap); void openbsd_vwarn(const char *fmt, va_list ap); void openbsd_vwarnx(const char *fmt, va_list ap); void openbsd_warnc(int code, const char *fmt, ...); void openbsd_warn(const char *fmt, ...); void openbsd_warnx(const char *fmt, ...); #endif /* ifndef _ERRC_H */ ================================================ FILE: openbsd/errx.c ================================================ /* $OpenBSD: errx.c,v 1.11 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #undef open void openbsd_errx(int eval, const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_verrx(eval, fmt, ap); va_end(ap); } ================================================ FILE: openbsd/getopt_long.c ================================================ /* $OpenBSD: getopt_long.c,v 1.32 2020/05/27 22:25:09 schwarze Exp $ */ /* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ /* SPDX-License-Identifier: ISC AND BSD-2-Clause */ /* * Copyright (c) 2002 Todd C. Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F39502-99-1-0512. */ /* * Copyright (c) 2000 The NetBSD Foundation, Inc. * * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ #include "../include/compat.h" #include "libgen.h" #include "getopt_long.h" #include #include #include #include #include #include "errc.h" #undef open int openbsd_opterr = 1; /* if error message should be printed */ int openbsd_optind = 1; /* index into parent argv vector */ int openbsd_optopt = '?'; /* character checked for validity */ int openbsd_optreset; /* reset getopt */ char *openbsd_optarg; /* argument associated with option */ #define PRINT_ERROR (( openbsd_opterr ) && ( *options != ':' )) #define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ #define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ #define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ /* return values */ #define BADCH (int)'?' #define BADARG (( *options == ':' ) ? (int)':' : (int)'?' ) #define INORDER (int)1 #define EMSG "" static int openbsd_getopt_internal(int, char *const *, const char *, const struct option *, int *, int); static int openbsd_parse_long_options(char *const *nargv, const char *options, const struct option *long_options, int *idx, int short_too, int flags); static int openbsd_gcd(int, int); static void openbsd_permute_args(int, int, int, char *const *); static char *place = EMSG; /* option letter processing */ static int openbsd_nonopt_start = -1; /* first non option argument (for permute) */ static int openbsd_nonopt_end = -1; /* first option after non options (for permute) */ /* Error messages */ static const char openbsd_recargchar[] = "option requires an argument -- %c"; static const char openbsd_recargstring[] = "option requires an argument -- %s"; static const char openbsd_ambig[] = "ambiguous option -- %.*s"; static const char openbsd_noarg[] = "option doesn't take an argument -- %.*s"; static const char openbsd_illoptchar[] = "unknown option -- %c"; static const char openbsd_illoptstring[] = "unknown option -- %s"; /* * Compute the greatest common divisor of a and b. */ static int openbsd_gcd(int a, int b) { int c; c = a % b; while (c != 0) { a = b; b = c; c = a % b; } return b; } /* * Exchange the block from nonopt_start to nonopt_end with the block * from nonopt_end to opt_end (keeping the same order of arguments * in each block). */ static void openbsd_permute_args(int panonopt_start, int panonopt_end, int opt_end, char *const *nargv) { int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; char *swap; /* * Compute lengths of blocks and number and size of cycles ... */ nnonopts = panonopt_end - panonopt_start; nopts = opt_end - panonopt_end; ncycle = openbsd_gcd(nnonopts, nopts); cyclelen = ( opt_end - panonopt_start ) / ncycle; for (i = 0; i < ncycle; i++) { cstart = panonopt_end + i; pos = cstart; for (j = 0; j < cyclelen; j++) { if (pos >= panonopt_end) { pos -= nnonopts; } else { pos += nopts; } swap = nargv[pos]; ((char **)nargv )[pos] = nargv[cstart]; ((char **)nargv )[cstart] = swap; } } } /* * openbsd_parse_long_options -- * Parse long options in argc/argv argument vector. * * Returns -1 if short_too is set and the option does not match long_options. */ static int openbsd_parse_long_options(char *const *nargv, const char *options, const struct option *long_options, int *idx, int short_too, int flags) { char *current_argv, *has_equal; size_t current_argv_len; int i, match, exact_match, second_partial_match; current_argv = place; match = -1; exact_match = 0; second_partial_match = 0; openbsd_optind++; if (( has_equal = strchr(current_argv, '=')) != NULL) { /* argument found (--option=arg) */ current_argv_len = has_equal - current_argv; has_equal++; } else { current_argv_len = strlen(current_argv); } for (i = 0; long_options[i].name; i++) { /* find matching long option */ if (strncmp(current_argv, long_options[i].name, current_argv_len)) { continue; } if (strlen(long_options[i].name) == current_argv_len) { /* exact match */ match = i; exact_match = 1; break; } /* * If this is a known short option, don't allow * a partial match of a single character. */ if (short_too && current_argv_len == 1) { continue; } if (match == -1) /* first partial match */ { match = i; } else if (( flags & FLAG_LONGONLY ) || long_options[i].has_arg != long_options[match].has_arg || long_options[i].flag != long_options[match].flag || long_options[i].val != long_options[match].val) { second_partial_match = 1; } } if (!exact_match && second_partial_match) { /* ambiguous abbreviation */ if (PRINT_ERROR) { openbsd_warnx(openbsd_ambig, (int)current_argv_len, current_argv); } openbsd_optopt = 0; return BADCH; } if (match != -1) { /* option found */ if (long_options[match].has_arg == no_argument && has_equal) { if (PRINT_ERROR) { openbsd_warnx(openbsd_noarg, (int)current_argv_len, current_argv); } /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) { openbsd_optopt = long_options[match].val; } else { openbsd_optopt = 0; } return BADARG; } if (long_options[match].has_arg == required_argument || long_options[match].has_arg == optional_argument) { if (has_equal) { openbsd_optarg = has_equal; } else if (long_options[match].has_arg == required_argument) { /* * Optional argument doesn't use next nargv ... */ openbsd_optarg = nargv[openbsd_optind++]; } } if (( long_options[match].has_arg == required_argument ) && ( openbsd_optarg == NULL )) { /* * Missing argument; leading ':' indicates no error * should be generated. */ if (PRINT_ERROR) { openbsd_warnx(openbsd_recargstring, current_argv); } /* * XXX: GNU sets optopt to val regardless of flag */ if (long_options[match].flag == NULL) { openbsd_optopt = long_options[match].val; } else { openbsd_optopt = 0; } --openbsd_optind; return BADARG; } } else { /* unknown option */ if (short_too) { --openbsd_optind; return -1; } if (PRINT_ERROR) { openbsd_warnx(openbsd_illoptstring, current_argv); } openbsd_optopt = 0; return BADCH; } if (idx) { *idx = match; } if (long_options[match].flag) { *long_options[match].flag = long_options[match].val; return 0; } else { return long_options[match].val; } } /* * openbsd_getopt_internal -- * Parse argc/argv argument vector. Called by user level routines. */ static int openbsd_getopt_internal(int nargc, char *const *nargv, const char *options, const struct option *long_options, int *idx, int flags) { char *oli; /* option letter list index */ int openbsd_optchar, short_too; static int posixly_correct = -1; if (options == NULL) { return -1; } /* * Some GNU programs (like cvs) set optind to 0 instead of * using optreset. Work around this braindamage. */ if (openbsd_optind == 0) { openbsd_optind = openbsd_optreset = 1; } /* * Disable GNU extensions if POSIXLY_CORRECT or POSIX_ME_HARDER * is set, or if the options string begins with a '+'. */ if (posixly_correct == -1 || openbsd_optreset) { posixly_correct = ( getenv("POSIXLY_CORRECT") != NULL ); } if (posixly_correct == -1 || openbsd_optreset) { posixly_correct = ( getenv("POSIX_ME_HARDER") != NULL ); } if (*options == '-') { flags |= FLAG_ALLARGS; } else if (posixly_correct || *options == '+') { flags &= ~FLAG_PERMUTE; } if (*options == '+' || *options == '-') { options++; } openbsd_optarg = NULL; if (openbsd_optreset) { openbsd_nonopt_start = openbsd_nonopt_end = -1; } start: if (openbsd_optreset || !*place) { /* update scanning pointer */ openbsd_optreset = 0; if (openbsd_optind >= nargc) { /* end of argument vector */ place = EMSG; if (openbsd_nonopt_end != -1) { /* do permutation, if we have to */ openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv); openbsd_optind -= openbsd_nonopt_end - openbsd_nonopt_start; } else if (openbsd_nonopt_start != -1) { /* * If we skipped non-options, set optind * to the first of them. */ openbsd_optind = openbsd_nonopt_start; } openbsd_nonopt_start = openbsd_nonopt_end = -1; return -1; } if (*( place = nargv[openbsd_optind] ) != '-' || ( place[1] == '\0' && strchr(options, '-') == NULL )) { place = EMSG; /* found non-option */ if (flags & FLAG_ALLARGS) { /* * GNU extension: * return non-option as argument to option 1 */ openbsd_optarg = nargv[openbsd_optind++]; return INORDER; } if (!( flags & FLAG_PERMUTE )) { /* * If no permutation wanted, stop parsing * at first non-option. */ return -1; } /* do permutation */ if (openbsd_nonopt_start == -1) { openbsd_nonopt_start = openbsd_optind; } else if (openbsd_nonopt_end != -1) { openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv); openbsd_nonopt_start = openbsd_optind - ( openbsd_nonopt_end - openbsd_nonopt_start ); openbsd_nonopt_end = -1; } openbsd_optind++; /* process next argument */ goto start; } if (openbsd_nonopt_start != -1 && openbsd_nonopt_end == -1) { openbsd_nonopt_end = openbsd_optind; } /* * If we have "-" do nothing, if "--" we are done. */ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { openbsd_optind++; place = EMSG; /* * We found an option (--), so if we skipped * non-options, we have to permute. */ if (openbsd_nonopt_end != -1) { openbsd_permute_args(openbsd_nonopt_start, openbsd_nonopt_end, openbsd_optind, nargv); openbsd_optind -= openbsd_nonopt_end - openbsd_nonopt_start; } openbsd_nonopt_start = openbsd_nonopt_end = -1; return -1; } } /* * Check long options if: * 1) we were passed some * 2) the arg is not just "-" * 3) either the arg starts with -- we are openbsd_getopt_long_only() */ if (long_options != NULL && place != nargv[openbsd_optind] && ( *place == '-' || ( flags & FLAG_LONGONLY ))) { short_too = 0; if (*place == '-') { place++; /* --foo long option */ } else if (*place != ':' && strchr(options, *place) != NULL) { short_too = 1; /* could be short option too */ } openbsd_optchar = openbsd_parse_long_options( nargv, options, long_options, idx, short_too, flags); if (openbsd_optchar != -1) { place = EMSG; return openbsd_optchar; } } if (( openbsd_optchar = (int)*place++ ) == (int)':' || ( oli = strchr(options, openbsd_optchar)) == NULL) { if (!*place) { ++openbsd_optind; } if (PRINT_ERROR) { openbsd_warnx(openbsd_illoptchar, openbsd_optchar); } openbsd_optopt = openbsd_optchar; return BADCH; } if (long_options != NULL && openbsd_optchar == 'W' && oli[1] == ';') { /* -W long-option */ if (*place) /* no space */ /* NOTHING */ { ; } else if (++openbsd_optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) { openbsd_warnx(openbsd_recargchar, openbsd_optchar); } openbsd_optopt = openbsd_optchar; return BADARG; } else /* white space */ { place = nargv[openbsd_optind]; } openbsd_optchar = openbsd_parse_long_options(nargv, options, long_options, idx, 0, flags); place = EMSG; return openbsd_optchar; } if (*++oli != ':') { /* doesn't take argument */ if (!*place) { ++openbsd_optind; } } else { /* takes (optional) argument */ openbsd_optarg = NULL; if (*place) /* no white space */ { openbsd_optarg = place; } else if (oli[1] != ':') { /* arg not optional */ if (++openbsd_optind >= nargc) { /* no arg */ place = EMSG; if (PRINT_ERROR) { openbsd_warnx(openbsd_recargchar, openbsd_optchar); } openbsd_optopt = openbsd_optchar; return BADARG; } else { openbsd_optarg = nargv[openbsd_optind]; } } place = EMSG; ++openbsd_optind; } /* dump back option letter */ return openbsd_optchar; } /* * opembsd_getopt -- * Parse argc/argv argument vector. */ int openbsd_getopt(int nargc, char *const *nargv, const char *options) { /* * We don't pass FLAG_PERMUTE to openbsd_getopt_internal() since * the BSD getopt(3) (unlike GNU) has never done this. * * Furthermore, since many privileged programs call openbsd_getopt() * before dropping privileges it makes sense to keep things * as simple (and bug-free) as possible. */ return openbsd_getopt_internal(nargc, nargv, options, NULL, NULL, 0); } /* * openbsd_getopt_long -- * Parse argc/argv argument vector. */ int openbsd_getopt_long(int nargc, char *const *nargv, const char *options, const struct option *long_options, int *idx) { return openbsd_getopt_internal( nargc, nargv, options, long_options, idx, FLAG_PERMUTE); } /* * getopt_long_only -- * Parse argc/argv argument vector. */ int openbsd_getopt_long_only(int nargc, char *const *nargv, const char *options, const struct option *long_options, int *idx) { return openbsd_getopt_internal( nargc, nargv, options, long_options, idx, FLAG_PERMUTE | FLAG_LONGONLY); } ================================================ FILE: openbsd/getopt_long.h ================================================ /* $OpenBSD: getopt.h,v 1.3 2013/11/22 21:32:49 millert Exp $ */ /* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ /* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2000 The NetBSD Foundation, Inc. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Dieter Baron and Thomas Klausner. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ #ifndef _OBSD_GETOPT_H_ # define _OBSD_GETOPT_H_ /* * GNU-like getopt_long() */ # define no_argument 0 # define required_argument 1 # define optional_argument 2 struct option { /* name of long option */ const char *name; /* * one of no_argument, required_argument, and optional_argument: * whether option takes an argument */ int has_arg; /* if not NULL, set *flag to val when option found */ int *flag; /* if flag not NULL, value to set *flag to; else return value */ int val; }; int openbsd_getopt_long (int, char *const *, const char *, const struct option *, int *); int openbsd_getopt_long_only (int, char *const *, const char *, const struct option *, int *); # ifndef _GETOPT_DEFINED_ # define _GETOPT_DEFINED_ int openbsd_getopt (int, char *const *, const char *); extern char *openbsd_optarg; /* getopt(3) external variables */ extern int openbsd_opterr; extern int openbsd_optind; extern int openbsd_optopt; extern int openbsd_optreset; # endif /* _GETOPT_DEFINED_ */ #endif /* !_OBSD_GETOPT_H_ */ ================================================ FILE: openbsd/getprogname.c ================================================ /* $OpenBSD: getprogname.c,v 1.4 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 2013 Antoine Jacoutot * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #ifdef _AIX # include # include # include #else extern char *__progname; #endif /* ifdef _AIX */ const char * bsd_getprogname(void) { #ifdef _AIX static char *p; static int first = 1; if (first) { first = 0; pid_t pid = getpid(); struct procentry64 procs; p = (0 < getprocs64 (&procs, sizeof procs, NULL, 0, &pid, 1) ? strdup (procs.pi_comm) : NULL); if (!p) p = "?"; } return p; #else return (__progname); #endif /* ifdef _AIX */ } ================================================ FILE: openbsd/issetugid.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022 Ørjan Malde * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include "../include/compat.h" #include #undef open #if ( defined(__GLIBC__) && defined(__GLIBC_MINOR__) ) \ || defined(__linux__) || defined(__midipix__) \ || defined(__illumos__) # include #else # include #endif /* if ( defined(__GLIBC__) && defined(__GLIBC_MINOR__) ) || defined(__linux__) || defined(__midipix__) || defined(__illumos__) */ #if defined(__FreeBSD__) || defined(__OpenBSD__) \ || ( defined(__APPLE__ ) && defined(__MACH__) ) \ || defined(__CYGWIN__) || defined(__NetBSD__) # include #else # if defined(__linux__) || defined(__illumos__) # include # endif /* if defined(__linux__) || defined(__illumos__) */ int issetugid(void) { int rv = 0; errno = 0; # if !defined(_AIX) \ && !defined(__illumos__) && !defined(__solaris__) \ && !defined(__managarm__) rv = getauxval(AT_SECURE) != 0; # endif /* if !defined(_AIX) && !defined(__illumos__) && !defined(__solaris__) */ if (errno) { errno = 0; rv = 1; } return rv; } #endif /* if defined(__FreeBSD__) || defined(__OpenBSD__) || ( defined(__APPLE__) && defined(__MACH__) ) || defined(__CYGWIN__) || defined(__NetBSD__) */ ================================================ FILE: openbsd/minpwcache.c ================================================ /* $OpenBSD: pwcache.c,v 1.16 2022/12/27 17:10:06 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992 Keith Muller * Copyright (c) 1992, 1993 The Regents of the University of California * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Keith Muller of the University of California, San Diego. * * 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. */ #include "../include/compat.h" #include #include #if defined(__illumos__) && !defined(_POSIX_PTHREAD_SEMANTICS) # define _POSIX_PTHREAD_SEMANTICS #endif /* if defined(__illumos__) && !defined(_POSIX_PTHREAD_SEMANTICS) */ #include #include #include #include #include #include /* * Constants and data structures used to implement group and password file * caches. Name lengths have been chosen to be as large as those supported * by the passwd and group files as well as the standard archive formats. * CACHE SIZES MUST BE PRIME */ #define UNMLEN 32 /* >= user name found in any protocol */ #define GNMLEN 32 /* >= group name found in any protocol */ #define UID_SZ 317 /* size of uid to user_name cache */ #define UNM_SZ 317 /* size of user_name to uid cache */ #define GID_SZ 251 /* size of gid to group_name cache */ #define GNM_SZ 251 /* size of group_name to gid cache */ #define VALID 1 /* entry and name are valid */ #define INVALID 2 /* entry valid, name NOT valid */ /* * Node structures used in the user, group, uid, and gid caches. */ typedef struct uidc { int valid; /* is this a valid or a miss entry */ char name[UNMLEN]; /* uid name */ uid_t uid; /* cached uid */ } UIDC; typedef struct gidc { int valid; /* is this a valid or a miss entry */ char name[GNMLEN]; /* gid name */ gid_t gid; /* cached gid */ } GIDC; /* * Routines that control user, group, uid and gid caches. * Traditional passwd/group cache routines perform quite poorly with * archives. The chances of hitting a valid lookup with an archive is quite a * bit worse than with files already resident on the file system. These misses * create a MAJOR performance cost. To address this problem, these routines * cache both hits and misses. */ static UIDC **usrtb; /* user name to uid cache */ static GIDC **grptb; /* group name to gid cache */ static unsigned int st_hash(const char *name, size_t len, int tabsz) { unsigned int key = 0; assert(name != NULL); while (len--) { key += *name++; key = ( key << 8 ) | ( key >> 24 ); } return key % tabsz; } /* * usrtb_start * creates an an empty usrtb * Return: * 0 if ok, -1 otherwise */ static int usrtb_start(void) { static int fail = 0; if (usrtb != NULL) { return 0; } if (fail) { return -1; } if (( usrtb = calloc(UNM_SZ, sizeof ( UIDC * ))) == NULL) { ++fail; return -1; } return 0; } /* * grptb_start * creates an an empty grptb * Return: * 0 if ok, -1 otherwise */ static int grptb_start(void) { static int fail = 0; if (grptb != NULL) { return 0; } if (fail) { return -1; } if (( grptb = calloc(GNM_SZ, sizeof ( GIDC * ))) == NULL) { ++fail; return -1; } return 0; } /* * uid_from_user() * caches the uid for a given user name. We use a simple hash table. * Return: * 0 if the user name is found (filling in uid), -1 otherwise */ int openbsd_uid_from_user(const char *name, uid_t *uid) { struct passwd pwstore, *pw = NULL; char pwbuf[_PW_BUF_LEN]; UIDC **pptr, *ptr = NULL; size_t namelen; /* * return -1 for mangled names */ if (name == NULL || (( namelen = strlen(name)) == 0 )) { return -1; } if (( usrtb != NULL ) || ( usrtb_start() == 0 )) { /* * look up in hash table, if found and valid return the uid, * if found and invalid, return a -1 */ pptr = usrtb + st_hash(name, namelen, UNM_SZ); ptr = *pptr; if (( ptr != NULL ) && ( ptr->valid > 0 ) && strcmp(name, ptr->name) == 0) { if (ptr->valid == INVALID) { return -1; } *uid = ptr->uid; return 0; } if (ptr == NULL) { *pptr = ptr = malloc(sizeof ( UIDC )); } } /* * no match, look it up, if no match store it as an invalid entry, * or store the matching uid */ getpwnam_r(name, &pwstore, pwbuf, sizeof ( pwbuf ), &pw); if (ptr == NULL) { if (pw == NULL) { return -1; } *uid = pw->pw_uid; return 0; } (void)openbsd_strlcpy(ptr->name, name, sizeof ( ptr->name )); if (pw == NULL) { ptr->valid = INVALID; return -1; } ptr->valid = VALID; *uid = ptr->uid = pw->pw_uid; return 0; } /* * gid_from_group() * caches the gid for a given group name. We use a simple hash table. * Return: * 0 if the group name is found (filling in gid), -1 otherwise */ int openbsd_gid_from_group(const char *name, gid_t *gid) { struct group grstore, *gr = NULL; char grbuf[_GR_BUF_LEN]; GIDC **pptr, *ptr = NULL; size_t namelen; /* * return -1 for mangled names */ if (name == NULL || (( namelen = strlen(name)) == 0 )) { return -1; } if (( grptb != NULL ) || ( grptb_start() == 0 )) { /* * look up in hash table, if found and valid return the uid, * if found and invalid, return a -1 */ pptr = grptb + st_hash(name, namelen, GID_SZ); ptr = *pptr; if (( ptr != NULL ) && ( ptr->valid > 0 ) && strcmp(name, ptr->name) == 0) { if (ptr->valid == INVALID) { return -1; } *gid = ptr->gid; return 0; } if (ptr == NULL) { *pptr = ptr = malloc(sizeof ( GIDC )); } } /* * no match, look it up, if no match store it as an invalid entry, * or store the matching gid */ getgrnam_r(name, &grstore, grbuf, sizeof ( grbuf ), &gr); if (ptr == NULL) { if (gr == NULL) { return -1; } *gid = gr->gr_gid; return 0; } (void)openbsd_strlcpy(ptr->name, name, sizeof ( ptr->name )); if (gr == NULL) { ptr->valid = INVALID; return -1; } ptr->valid = VALID; *gid = ptr->gid = gr->gr_gid; return 0; } ================================================ FILE: openbsd/minpwcache.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _MINPWCAC_H # define _MINPWCAC_H # include "../include/compat.h" # include # include # include # include # include # undef open int openbsd_gid_from_group(const char *name, gid_t *gid); int openbsd_uid_from_user(const char *name, uid_t *uid); #endif /* ifndef _MINPWCAC_H */ ================================================ FILE: openbsd/open.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include "../include/compat.h" #include #include #include #include #include #undef open int openbsd_open(const char *path, int flags, ...) { va_list ap; int fd, lock; lock = flags & ( O_EXLOCK | O_SHLOCK ); flags &= ~lock; va_start(ap, flags); if (( fd = open(path, flags, ap)) == -1) { return -1; } va_end(ap); if (lock == 0) { return fd; } #ifndef __solaris__ if (flock(fd, lock & O_EXLOCK ? LOCK_EX : LOCK_SH) == -1) { close(fd); return -1; } #endif /* ifndef __solaris__ */ return fd; } ================================================ FILE: openbsd/pledge.c ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2021-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #include "../include/compat.h" #include #undef open #ifdef __OpenBSD__ # include #endif /* ifdef __OpenBSD__ */ int openbsd_pledge(const char *promises, const char *execpromises) { #if defined(__OpenBSD__) || defined(__serenity__) # if ( defined(OpenBSD) && OpenBSD >= 201605 ) \ || defined(OpenBSD5_9) || defined(__serenity__) return(pledge(promises, execpromises)); # else return 0; # endif /* if ( defined(OpenBSD) && OpenBSD >= 201605 ) || defined(OpenBSD5_9) || defined(__serenity__) */ #else return 0; #endif /* if defined(__OpenBSD__) || defined(__serenity__) */ } ================================================ FILE: openbsd/reallocarray.c ================================================ /* $OpenBSD: reallocarray.c,v 1.3 2015/09/13 08:31:47 guenther Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 2008 Otto Moerbeek * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../include/compat.h" #include #include #include #include #undef open /* * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW */ #ifdef MUL_NO_OVERFLOW # undef MUL_NO_OVERFLOW #endif /* ifdef MUL_NO_OVERFLOW */ #define MUL_NO_OVERFLOW ((size_t)1 << ( sizeof ( size_t ) * 4 )) void * openbsd_reallocarray(void *optr, size_t nmemb, size_t size) { if (( nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW ) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return NULL; } return realloc(optr, size * nmemb); } ================================================ FILE: openbsd/setmode.c ================================================ /* $OpenBSD: setmode.c,v 1.22 2014/10/11 04:14:35 deraadt Exp $ */ /* $NetBSD: setmode.c,v 1.15 1997/02/07 22:21:06 christos Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1989, 1993, 1994 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Dave Borman at Cray Research, Inc. * * 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. */ #include "../include/compat.h" #include #include #include #include #include #include #ifdef __solaris__ # undef __EXTENSIONS__ # define __EXTENSIONS__ # define _XPG7 # include # undef __EXTENSIONS__ # include #else # include #endif /* ifdef __solaris__ */ #undef open #define SET_LEN 6 /* initial # of bitcmd struct to malloc */ #define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */ typedef struct bitcmd { char cmd; char cmd2; mode_t bits; } BITCMD; #define CMD2_CLR 0x01 #define CMD2_SET 0x02 #define CMD2_GBITS 0x04 #define CMD2_OBITS 0x08 #define CMD2_UBITS 0x10 static BITCMD *addcmd(BITCMD *, int, int, int, unsigned int); static void compress_mode(BITCMD *); /* * Given the old mode and an array of bitcmd structures, apply the operations * described in the bitcmd structures to the old mode, and return the new mode. * Note that there is no '=' command; a strict assignment is just a '-' (clear * bits) followed by a '+' (set bits). */ mode_t openbsd_getmode(const void *bbox, mode_t omode) { const BITCMD *set; mode_t clrval, newmode, value; set = (const BITCMD *)bbox; newmode = omode; for (value = 0;; set++) { (void)value; switch (set->cmd) { /* * When copying the user, group or other bits around, we "know" * where the bits are in the mode so that we can do shifts to * copy them around. If we don't use shifts, it gets real * grundgy with lots of single bit checks and bit sets. */ case 'u': value = ( newmode & S_IRWXU ) >> 6; (void)value; goto common; case 'g': value = ( newmode & S_IRWXG ) >> 3; (void)value; goto common; case 'o': value = newmode & S_IRWXO; (void)value; common: if (set->cmd2 & CMD2_CLR) { clrval = ( set->cmd2 & CMD2_SET ) ? S_IRWXO : value; if (set->cmd2 & CMD2_UBITS) { newmode &= ~(( clrval << 6 ) & set->bits ); } if (set->cmd2 & CMD2_GBITS) { newmode &= ~(( clrval << 3 ) & set->bits ); } if (set->cmd2 & CMD2_OBITS) { newmode &= ~( clrval & set->bits ); } } if (set->cmd2 & CMD2_SET) { if (set->cmd2 & CMD2_UBITS) { newmode |= ( value << 6 ) & set->bits; } if (set->cmd2 & CMD2_GBITS) { newmode |= ( value << 3 ) & set->bits; } if (set->cmd2 & CMD2_OBITS) { newmode |= value & set->bits; } } break; case '+': newmode |= set->bits; break; case '-': newmode &= ~set->bits; break; case 'X': if (omode & ( S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH )) { newmode |= set->bits; } break; case '\0': default: return newmode; } } } #define ADDCMD(a, b, c, d) \ if (set >= endset) \ { \ BITCMD *newset; \ setlen += SET_LEN_INCR; \ newset = openbsd_reallocarray(saveset, setlen, sizeof ( BITCMD )); \ if (newset == NULL) \ { \ free(saveset); \ return NULL; \ } \ set = newset + ( set - saveset ); \ saveset = newset; \ endset = newset + ( setlen - 2 ); \ } \ set = addcmd(set, ( a ), ( b ), ( c ), ( d )) #define STANDARD_BITS ( S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO ) void * openbsd_setmode(const char *p) { char op, *ep; BITCMD *set, *saveset, *endset; sigset_t sigset, sigoset; mode_t mask, perm, permXbits, who; int equalopdone, setlen; unsigned long perml; if (!*p) { errno = EINVAL; return NULL; } /* * Get a copy of the mask for the permissions that are mask relative. * Flip the bits, we want what's not set. Since it's possible that * the caller is opening files inside a signal handler, protect them * as best we can. */ sigfillset(&sigset); (void)sigprocmask(SIG_BLOCK, &sigset, &sigoset); (void)umask(mask = umask(0)); mask = ~mask; (void)sigprocmask(SIG_SETMASK, &sigoset, NULL); setlen = SET_LEN + 2; if (( set = calloc((unsigned int)sizeof ( BITCMD ), setlen)) == NULL) { return NULL; } saveset = set; endset = set + ( setlen - 2 ); /* * If an absolute number, get it and return; disallow non-octal digits * or illegal bits. */ if (isdigit((unsigned char)*p)) { perml = strtoul(p, &ep, 8); /* The test on perml will also catch overflow. */ if (*ep != '\0' || ( perml & ~( STANDARD_BITS | S_ISTXT ))) { free(saveset); errno = ERANGE; return NULL; } perm = (mode_t)perml; (void)perm; ADDCMD('=', ( STANDARD_BITS | S_ISTXT ), perm, mask); set->cmd = 0; return saveset; } /* * Build list of structures to set/clear/copy bits as described by * each clause of the symbolic mode. */ for (;;) { /* First, find out which bits might be modified. */ for (who = 0;; ++p) { switch (*p) { case 'a': who |= STANDARD_BITS; break; case 'u': who |= S_ISUID | S_IRWXU; break; case 'g': who |= S_ISGID | S_IRWXG; break; case 'o': who |= S_IRWXO; break; default: goto getop; } } getop: if (( op = *p++ ) != '+' && op != '-' && op != '=') { free(saveset); errno = EINVAL; return NULL; } if (op == '=') { equalopdone = 0; } who &= ~S_ISTXT; for (perm = 0, permXbits = 0;; ++p) { switch (*p) { case 'r': perm |= S_IRUSR | S_IRGRP | S_IROTH; break; case 's': /* * If specific bits where requested and * only "other" bits ignore set-id. */ if (who == 0 || ( who & ~S_IRWXO )) { perm |= S_ISUID | S_ISGID; } break; case 't': /* * If specific bits where requested and * only "other" bits ignore sticky. */ if (who == 0 || ( who & ~S_IRWXO )) { who |= S_ISTXT; perm |= S_ISTXT; } break; case 'w': perm |= S_IWUSR | S_IWGRP | S_IWOTH; break; case 'X': permXbits = S_IXUSR | S_IXGRP | S_IXOTH; (void)permXbits; break; case 'x': perm |= S_IXUSR | S_IXGRP | S_IXOTH; break; case 'u': case 'g': case 'o': /* * When ever we hit 'u', 'g', or 'o', we have * to flush out any partial mode that we have, * and then do the copying of the mode bits. */ if (perm) { ADDCMD(op, who, perm, mask); perm = 0; (void)perm; } if (op == '=') { equalopdone = 1; } if (op == '+' && permXbits) { ADDCMD('X', who, permXbits, mask); permXbits = 0; (void)permXbits; } ADDCMD(*p, who, op, mask); break; default: /* * Add any permissions that we haven't already * done. */ if (perm || ( op == '=' && !equalopdone )) { if (op == '=') { equalopdone = 1; } ADDCMD(op, who, perm, mask); perm = 0; (void)perm; } if (permXbits) { ADDCMD('X', who, permXbits, mask); permXbits = 0; (void)permXbits; } goto apply; } } apply: if (!*p) { break; } if (*p != ',') { goto getop; } ++p; } set->cmd = 0; compress_mode(saveset); return saveset; } static BITCMD * addcmd(BITCMD *set, int op, int who, int oparg, unsigned int mask) { switch (op) { case '=': set->cmd = '-'; set->bits = who ? who : STANDARD_BITS; set++; op = '+'; /* FALLTHROUGH */ case '+': case '-': case 'X': set->cmd = op; set->bits = ( who ? who : mask ) & oparg; break; case 'u': case 'g': case 'o': set->cmd = op; if (who) { set->cmd2 = (( who & S_IRUSR ) ? CMD2_UBITS : 0 ) | (( who & S_IRGRP ) ? CMD2_GBITS : 0 ) | (( who & S_IROTH ) ? CMD2_OBITS : 0 ); set->bits = ( mode_t ) ~0; } else { set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS; set->bits = mask; } if (oparg == '+') { set->cmd2 |= CMD2_SET; } else if (oparg == '-') { set->cmd2 |= CMD2_CLR; } else if (oparg == '=') { set->cmd2 |= CMD2_SET | CMD2_CLR; } break; } return set + 1; } /* * Given an array of bitcmd structures, compress by compacting consecutive * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u', * 'g' and 'o' commands continue to be separate. They could probably be * compacted, but it's not worth the effort. */ static void compress_mode(BITCMD *set) { BITCMD *nset; int setbits, clrbits, Xbits, op; for (nset = set;;) { /* Copy over any 'u', 'g' and 'o' commands. */ while (( op = nset->cmd ) != '+' && op != '-' && op != 'X') { *set++ = *nset++; if (!op) { return; } } for (setbits = clrbits = Xbits = 0;; nset++) { if (( op = nset->cmd ) == '-') { clrbits |= nset->bits; setbits &= ~nset->bits; Xbits &= ~nset->bits; } else if (op == '+') { setbits |= nset->bits; clrbits &= ~nset->bits; Xbits &= ~nset->bits; } else if (op == 'X') { Xbits |= nset->bits & ~setbits; } else { break; } } if (clrbits) { set->cmd = '-'; set->cmd2 = 0; set->bits = clrbits; set++; } if (setbits) { set->cmd = '+'; set->cmd2 = 0; set->bits = setbits; set++; } if (Xbits) { set->cmd = 'X'; set->cmd2 = 0; set->bits = Xbits; set++; } } } ================================================ FILE: openbsd/setmode.h ================================================ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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 names of the copyright holders nor the names of any * 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. */ #ifndef _SETMODE_H # define _SETMODE_H # include "../include/compat.h" # include # include # include # include # include # undef open void *openbsd_setmode(const char *p); mode_t openbsd_getmode(const void *bbox, mode_t omode); #endif /* ifndef _SETMODE_H */ ================================================ FILE: openbsd/strlcat.c ================================================ /* $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 1998, 2015 Todd C. Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../include/compat.h" #include #include #include #undef open /* * Appends src to string dst of size dsize (unlike strncat, dsize is the * full size of dst, not space left). At most dsize-1 characters * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). * Returns strlen(src) + MIN(dsize, strlen(initial dst)). * If retval >= dsize, truncation occurred. */ size_t openbsd_strlcat(char *dst, const char *src, size_t dsize) { const char *odst = dst; const char *osrc = src; size_t n = dsize; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end. */ while (n-- != 0 && *dst != '\0') { dst++; } dlen = dst - odst; n = dsize - dlen; if (n-- == 0) { return dlen + strlen(src); } while (*src != '\0') { if (n != 0) { *dst++ = *src; n--; } src++; } *dst = '\0'; return dlen + ( src - osrc ); /* count does not include NUL */ } ================================================ FILE: openbsd/strlcpy.c ================================================ /* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 1998, 2015 Todd C. Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "../include/compat.h" #include #include #undef open /* * Copy string src to buffer dst of size dsize. At most dsize-1 * chars will be copied. Always NUL terminates (unless dsize == 0). * Returns strlen(src); if retval >= dsize, truncation occurred. */ size_t openbsd_strlcpy(char *dst, const char *src, size_t dsize) { const char *osrc = src; size_t nleft = dsize; /* Copy as many bytes as will fit. */ if (nleft != 0) { while (--nleft != 0) { if (( *dst++ = *src++ ) == '\0') { break; } } } /* Not enough room in dst, add NUL and traverse rest of src. */ if (nleft == 0) { if (dsize != 0) { *dst = '\0'; /* NUL-terminate dst */ } while (*src++) { ; } } return src - osrc - 1; /* count does not include NUL */ } ================================================ FILE: openbsd/strtonum.c ================================================ /* $OpenBSD: strtonum.c,v 1.8 2015/09/13 08:31:48 guenther Exp $ */ /* SPDX-License-Identifier: ISC */ /* * Copyright (c) 2004 Ted Unangst and Todd Miller * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #define INVALID 1 #define TOOSMALL 2 #define TOOLARGE 3 long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp) { long long ll = 0; int error = 0; char *ep; struct errval { const char *errstr; int err; } ev[4] = { { NULL, 0 }, { "invalid", EINVAL }, { "too small", ERANGE }, { "too large", ERANGE }, }; ev[0].err = errno; errno = 0; if (minval > maxval) { error = INVALID; } else { ll = strtoll(numstr, &ep, 10); if (numstr == ep || *ep != '\0') error = INVALID; else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) error = TOOSMALL; else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) error = TOOLARGE; } if (errstrp != NULL) *errstrp = ev[error].errstr; errno = ev[error].err; if (error) ll = 0; return (ll); } ================================================ FILE: openbsd/verr.c ================================================ /* $OpenBSD: verr.c,v 1.11 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #include #include #include #include #undef open void openbsd_verr(int eval, const char *fmt, va_list ap) { int sverrno; sverrno = errno; (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) { (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, ": "); } (void)fprintf(stderr, "%s\n", strerror(sverrno)); exit(eval); } ================================================ FILE: openbsd/verrc.c ================================================ /* $OpenBSD: verrc.c,v 1.3 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "bsd_err.h" #include #include #include #include #undef open void openbsd_verrc(int eval, int code, const char *fmt, va_list ap) { (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) { (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, ": "); } (void)fprintf(stderr, "%s\n", strerror(code)); exit(eval); } ================================================ FILE: openbsd/verrx.c ================================================ /* $OpenBSD: verrx.c,v 1.11 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #include #include #undef open void openbsd_verrx(int eval, const char *fmt, va_list ap) { (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, "\n"); exit(eval); } ================================================ FILE: openbsd/vwarn.c ================================================ /* $OpenBSD: vwarn.c,v 1.11 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #include #include #include #include #undef open void openbsd_vwarn(const char *fmt, va_list ap) { int sverrno; sverrno = errno; (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) { (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, ": "); } (void)fprintf(stderr, "%s\n", strerror(sverrno)); } ================================================ FILE: openbsd/vwarnc.c ================================================ /* $OpenBSD: vwarnc.c,v 1.3 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include #include #include #include #include #undef open void openbsd_vwarnc(int code, const char *fmt, va_list ap) { (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) { (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, ": "); } (void)fprintf(stderr, "%s\n", strerror(code)); } ================================================ FILE: openbsd/vwarnx.c ================================================ /* $OpenBSD: vwarnx.c,v 1.11 2016/03/13 18:34:20 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #include #include #undef open void openbsd_vwarnx(const char *fmt, va_list ap) { (void)fprintf(stderr, "%s: ", bsd_getprogname()); if (fmt != NULL) (void)vfprintf(stderr, fmt, ap); (void)fprintf(stderr, "\n"); } ================================================ FILE: openbsd/warn.c ================================================ /* $OpenBSD: warn.c,v 1.11 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #undef open void openbsd_warn(const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_vwarn(fmt, ap); va_end(ap); } ================================================ FILE: openbsd/warnc.c ================================================ /* $OpenBSD: warnc.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #undef open void openbsd_warnc(int code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_vwarnc(code, fmt, ap); va_end(ap); } ================================================ FILE: openbsd/warnx.c ================================================ /* $OpenBSD: warnx.c,v 1.10 2015/08/31 02:53:57 guenther Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993 The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #include "errc.h" #include #include #undef open void openbsd_warnx(const char *fmt, ...) { va_list ap; va_start(ap, fmt); openbsd_vwarnx(fmt, ap); va_end(ap); } ================================================ FILE: regex/bsd_regex2.h ================================================ /* $OpenBSD: regex2.h,v 1.12 2021/01/03 17:07:58 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)regex2.h 8.4 (Berkeley) 3/20/94 */ /* * internals of regex_t */ #define MAGIC1 ((( 'r' ^ 0200 ) << 8 ) | 'e' ) /* * The internal representation is a *strip*, a sequence of * operators ending with an endmarker. (Some terminology etc. is a * historical relic of earlier versions which used multiple strips.) * Certain oddities in the representation are there to permit running * the machinery backwards; in particular, any deviation from sequential * flow must be marked at both its source and its destination. Some * fine points: * * - OPLUS_ and O_PLUS are *inside* the loop they create. * - OQUEST_ and O_QUEST are *outside* the bypass they create. * - OCH_ and O_CH are *outside* the multi-way branch they create, while * OOR1 and OOR2 are respectively the end and the beginning of one of * the branches. Note that there is an implicit OOR2 following OCH_ * and an implicit OOR1 preceding O_CH. * * In state representations, an operator's bit is on to signify a state * immediately *preceding* "execution" of that operator. */ typedef unsigned long sop; /* strip operator */ typedef long sopno; #define OPRMASK 0xf8000000LU #define OPDMASK 0x07ffffffLU #define OPSHIFT ((unsigned)27 ) #define OP(n) (( n ) & OPRMASK ) #define OPND(n) (( n ) & OPDMASK ) #define SOP(op, opnd) (( op ) | ( opnd )) /* operators meaning operand */ /* (back, fwd are offsets) */ #define OEND ( 1LU << OPSHIFT ) /* endmarker - */ #define OCHAR ( 2LU << OPSHIFT ) /* character unsigned char */ #define OBOL ( 3LU << OPSHIFT ) /* left anchor - */ #define OEOL ( 4LU << OPSHIFT ) /* right anchor - */ #define OANY ( 5LU << OPSHIFT ) /* . - */ #define OANYOF ( 6LU << OPSHIFT ) /* [...] set number */ #define OBACK_ ( 7LU << OPSHIFT ) /* begin \d paren number */ #define O_BACK ( 8LU << OPSHIFT ) /* end \d paren number */ #define OPLUS_ ( 9LU << OPSHIFT ) /* + prefix fwd to suffix */ #define O_PLUS ( 10LU << OPSHIFT ) /* + suffix back to prefix */ #define OQUEST_ ( 11LU << OPSHIFT ) /* ? prefix fwd to suffix */ #define O_QUEST ( 12LU << OPSHIFT ) /* ? suffix back to prefix */ #define OLPAREN ( 13LU << OPSHIFT ) /* ( fwd to ) */ #define ORPAREN ( 14LU << OPSHIFT ) /* ) back to ( */ #define OCH_ ( 15LU << OPSHIFT ) /* begin choice fwd to OOR2 */ #define OOR1 ( 16LU << OPSHIFT ) /* | pt. 1 back to OOR1 or OCH_ */ #define OOR2 ( 17LU << OPSHIFT ) /* | pt. 2 fwd to OOR2 or O_CH */ #define O_CH ( 18LU << OPSHIFT ) /* end choice back to OOR1 */ #define OBOW ( 19LU << OPSHIFT ) /* begin word - */ #define OEOW ( 20LU << OPSHIFT ) /* end word - */ /* * Structure for [] character-set representation. Character sets are * done as bit vectors, grouped 8 to a byte vector for compactness. * The individual set therefore has both a pointer to the byte vector * and a mask to pick out the relevant bit of each byte. A hash code * simplifies testing whether two sets could be identical. * * This will get trickier for multicharacter collating elements. As * preliminary hooks for dealing with such things, we also carry along * a string of multi-character elements, and decide the size of the * vectors at run time. */ typedef struct { uch *ptr; /* -> uch [csetsize] */ uch mask; /* bit within array */ uch hash; /* hash code */ } cset; static inline void CHadd(cset *cs, char c) { cs->ptr[(uch)c] |= cs->mask; cs->hash += c; } static inline void CHsub(cset *cs, char c) { cs->ptr[(uch)c] &= ~cs->mask; cs->hash -= c; } static inline int CHIN(const cset *cs, char c) { return ( cs->ptr[(uch)c] & cs->mask ) != 0; } /* * main compiled-expression structure */ struct re_guts { int magic; #define MAGIC2 ((( 'R' ^ 0200 ) << 8 ) | 'E' ) sop *strip; /* malloced area for strip */ int csetsize; /* number of bits in a cset vector */ int ncsets; /* number of csets in use */ cset *sets; /* -> cset [ncsets] */ uch *setbits; /* -> uch[csetsize][ncsets/CHAR_BIT] */ int cflags; /* copy of regcomp() cflags argument */ sopno nstates; /* = number of sops */ sopno firststate; /* the initial OEND (normally 0) */ sopno laststate; /* the final OEND */ int iflags; /* internal flags */ #define USEBOL 01 /* used ^ */ #define USEEOL 02 /* used $ */ #define BAD 04 /* something wrong */ int nbol; /* number of ^ used */ int neol; /* number of $ used */ char *must; /* match must contain this string */ int mlen; /* length of must */ size_t nsub; /* copy of re_nsub */ int backrefs; /* does it use back references? */ sopno nplus; /* how deep does it nest +s? */ }; /* misc utilities */ #define OUT ( CHAR_MAX + 1 ) /* a non-character value */ #define ISWORD(c) ( isalnum(c) || ( c ) == '_' ) ================================================ FILE: regex/cclass.h ================================================ /* $OpenBSD: cclass.h,v 1.7 2020/12/30 08:54:42 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)cclass.h 8.3 (Berkeley) 3/20/94 */ /* character-class table */ static const struct cclass { const char *name; const char *chars; } cclasses[] = { { "alnum", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\ 0123456789" }, { "alpha", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" }, { "blank", " \t" }, { "cntrl", "\007\b\t\n\v\f\r\1\2\3\4\5\6\16\17\20\21\22\23\24\ \25\26\27\30\31\32\33\34\35\36\37\177" }, { "digit", "0123456789" }, { "graph", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\ 0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" }, { "lower", "abcdefghijklmnopqrstuvwxyz" }, { "print", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\ 0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ " }, { "punct", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" }, { "space", "\t\n\v\f\r " }, { "upper", "ABCDEFGHIJKLMNOPQRSTUVWXYZ" }, { "xdigit", "0123456789ABCDEFabcdef" }, { NULL, 0 } }; ================================================ FILE: regex/cname.h ================================================ /* $OpenBSD: cname.h,v 1.6 2020/12/30 08:53:30 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)cname.h 8.3 (Berkeley) 3/20/94 */ /* character-name table */ static const struct cname { const char *name; char code; } cnames[] = { { "NUL", '\0' }, { "SOH", '\001' }, { "STX", '\002' }, { "ETX", '\003' }, { "EOT", '\004' }, { "ENQ", '\005' }, { "ACK", '\006' }, { "BEL", '\007' }, { "alert", '\007' }, { "BS", '\010' }, { "backspace", '\b' }, { "HT", '\011' }, { "tab", '\t' }, { "LF", '\012' }, { "newline", '\n' }, { "VT", '\013' }, { "vertical-tab", '\v' }, { "FF", '\014' }, { "form-feed", '\f' }, { "CR", '\015' }, { "carriage-return", '\r' }, { "SO", '\016' }, { "SI", '\017' }, { "DLE", '\020' }, { "DC1", '\021' }, { "DC2", '\022' }, { "DC3", '\023' }, { "DC4", '\024' }, { "NAK", '\025' }, { "SYN", '\026' }, { "ETB", '\027' }, { "CAN", '\030' }, { "EM", '\031' }, { "SUB", '\032' }, { "ESC", '\033' }, { "IS4", '\034' }, { "FS", '\034' }, { "IS3", '\035' }, { "GS", '\035' }, { "IS2", '\036' }, { "RS", '\036' }, { "IS1", '\037' }, { "US", '\037' }, { "space", ' ' }, { "exclamation-mark", '!' }, { "quotation-mark", '"' }, { "number-sign", '#' }, { "dollar-sign", '$' }, { "percent-sign", '%' }, { "ampersand", '&' }, { "apostrophe", '\'' }, { "left-parenthesis", '(' }, { "right-parenthesis", ')' }, { "asterisk", '*' }, { "plus-sign", '+' }, { "comma", ',' }, { "hyphen", '-' }, { "hyphen-minus", '-' }, { "period", '.' }, { "full-stop", '.' }, { "slash", '/' }, { "solidus", '/' }, { "zero", '0' }, { "one", '1' }, { "two", '2' }, { "three", '3' }, { "four", '4' }, { "five", '5' }, { "six", '6' }, { "seven", '7' }, { "eight", '8' }, { "nine", '9' }, { "colon", ':' }, { "semicolon", ';' }, { "less-than-sign", '<' }, { "equals-sign", '=' }, { "greater-than-sign", '>' }, { "question-mark", '?' }, { "commercial-at", '@' }, { "left-square-bracket", '[' }, { "backslash", '\\' }, { "reverse-solidus", '\\' }, { "right-square-bracket", ']' }, { "circumflex", '^' }, { "circumflex-accent", '^' }, { "underscore", '_' }, { "low-line", '_' }, { "grave-accent", '`' }, { "left-brace", '{' }, { "left-curly-bracket", '{' }, { "vertical-line", '|' }, { "right-brace", '}' }, { "right-curly-bracket", '}' }, { "tilde", '~' }, { "DEL", '\177' }, { NULL, 0 } }; ================================================ FILE: regex/engine.c ================================================ /* $OpenBSD: engine.c,v 1.26 2020/12/28 21:41:55 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)engine.c 8.5 (Berkeley) 3/20/94 */ /* * The matching engine and friends. This file is #included by regexec.c * after suitable #defines of a variety of macros used herein, so that * different state representations can be used without duplicating masses * of code. */ #ifdef SNAMES # define matcher smatcher # define fast sfast # define slow sslow # define dissect sdissect # define backref sbackref # define step sstep # define print sprint # define at sat # define match smat # define nope snope #endif /* ifdef SNAMES */ #ifdef LNAMES # define matcher lmatcher # define fast lfast # define slow lslow # define dissect ldissect # define backref lbackref # define step lstep # define print lprint # define at lat # define match lmat # define nope lnope #endif /* ifdef LNAMES */ /* another structure passed up and down to avoid zillions of parameters */ struct match { struct re_guts *g; int eflags; regmatch_t *pmatch; /* [nsub+1] (0 element unused) */ const char *offp; /* offsets work from here */ const char *beginp; /* start of string -- virtual NUL precedes */ const char *endp; /* end of string -- virtual NUL here */ const char *coldp; /* can be no match starting before here */ const char **lastpos; /* [nplus+1] */ STATEVARS; states st; /* current states */ states fresh; /* states for a fresh start */ states tmp; /* temporary */ states empty; /* empty set of states */ }; static int matcher(struct re_guts *, const char *, size_t, regmatch_t[], int); static const char *dissect(struct match *, const char *, const char *, sopno, sopno); static const char *backref(struct match *, const char *, const char *, sopno, sopno, sopno, int); static const char *fast(struct match *, const char *, const char *, sopno, sopno); static const char *slow(struct match *, const char *, const char *, sopno, sopno); static states step(struct re_guts *, sopno, sopno, states, int, states); #define MAX_RECURSION 100 #define BOL ( OUT + 1 ) #define EOL ( BOL + 1 ) #define BOLEOL ( BOL + 2 ) #define NOTHING ( BOL + 3 ) #define BOW ( BOL + 4 ) #define EOW ( BOL + 5 ) /* update nonchars[] array below when adding fake chars here */ #define CODEMAX ( BOL + 5 ) /* highest code used */ #define NONCHAR(c) (( c ) > CHAR_MAX ) #define NNONCHAR ( CODEMAX - CHAR_MAX ) #ifdef REDEBUG static void print(struct match *, const char *, states, int, FILE *); #endif /* ifdef REDEBUG */ #ifdef REDEBUG static void at(struct match *, const char *, const char *, const char *, sopno, sopno); #endif /* ifdef REDEBUG */ #ifdef REDEBUG static const char *pchar(int); #endif /* ifdef REDEBUG */ #ifdef REDEBUG # define SP(t, s, c) print(m, t, s, c, stdout) # define AT(t, p1, p2, s1, s2) at(m, t, p1, p2, s1, s2) # define NOTE(str) \ { \ if (m->eflags & REG_TRACE) \ (void)printf("=%s\n", ( str )); \ } static int nope = 0; #else /* ifdef REDEBUG */ # define SP(t, s, c) /* nothing */ # define AT(t, p1, p2, s1, s2) /* nothing */ # define NOTE(s) /* nothing */ #endif /* ifdef REDEBUG */ /* * - matcher - the actual matching engine */ static int /* 0 success, REG_NOMATCH failure */ matcher(struct re_guts *g, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) { const char *endp; int i; struct match mv; struct match *m = &mv; const char *dp; const sopno gf = g->firststate + 1; /* +1 for OEND */ const sopno gl = g->laststate; const char *start; const char *stop; /* simplify the situation where possible */ if (g->cflags & REG_NOSUB) { nmatch = 0; } if (eflags & REG_STARTEND) { start = string + pmatch[0].rm_so; stop = string + pmatch[0].rm_eo; } else { start = string; stop = start + strlen(start); } if (stop < start) { return REG_INVARG; } /* prescreening; this does wonders for this rather slow code */ if (g->must != NULL) { for (dp = start; dp < stop; dp++) { if (*dp == g->must[0] && stop - dp >= g->mlen && memcmp(dp, g->must, g->mlen) == 0) { break; } } if (dp == stop) /* we didn't find g->must */ { return REG_NOMATCH; } } /* match struct setup */ m->g = g; m->eflags = eflags; m->pmatch = NULL; m->lastpos = NULL; m->offp = string; m->beginp = start; m->endp = stop; STATESETUP(m, 4); SETUP(m->st); SETUP(m->fresh); SETUP(m->tmp); SETUP(m->empty); CLEAR(m->empty); /* this loop does only one repetition except for backrefs */ for (;;) { endp = fast(m, start, stop, gf, gl); if (endp == NULL) { /* a miss */ free(m->pmatch); free(m->lastpos); STATETEARDOWN(m); return REG_NOMATCH; } if (nmatch == 0 && !g->backrefs) { break; /* no further info needed */ } /* where? */ assert(m->coldp != NULL); for (;;) { NOTE("finding start"); endp = slow(m, m->coldp, stop, gf, gl); if (endp != NULL) { break; } assert(m->coldp < m->endp); m->coldp++; } if (nmatch == 1 && !g->backrefs) { break; /* no further info needed */ } /* oh my, he wants the subexpressions... */ if (m->pmatch == NULL) { m->pmatch = openbsd_reallocarray( NULL, m->g->nsub + 1, sizeof ( regmatch_t ) ); } if (m->pmatch == NULL) { STATETEARDOWN(m); return REG_ESPACE; } for (i = 1; i <= m->g->nsub; i++) { m->pmatch[i].rm_so = m->pmatch[i].rm_eo = -1; } if (!g->backrefs && !( m->eflags & REG_BACKR )) { NOTE("dissecting"); dp = dissect(m, m->coldp, endp, gf, gl); } else { if (g->nplus > 0 && m->lastpos == NULL) { m->lastpos = openbsd_reallocarray( NULL, g->nplus + 1, sizeof ( char * ) ); } if (g->nplus > 0 && m->lastpos == NULL) { free(m->pmatch); STATETEARDOWN(m); return REG_ESPACE; } NOTE("backref dissect"); dp = backref(m, m->coldp, endp, gf, gl, (sopno)0, 0); } if (dp != NULL) { break; } /* uh-oh... we couldn't find a subexpression-level match */ assert(g->backrefs); /* must be back references doing it */ assert(g->nplus == 0 || m->lastpos != NULL); for (;;) { if (dp != NULL || endp <= m->coldp) { break; /* defeat */ } NOTE("backoff"); endp = slow(m, m->coldp, endp - 1, gf, gl); if (endp == NULL) { break; /* defeat */ } /* try it on a shorter possibility */ #ifndef NDEBUG for (i = 1; i <= m->g->nsub; i++) { assert(m->pmatch[i].rm_so == -1); assert(m->pmatch[i].rm_eo == -1); } #endif /* ifndef NDEBUG */ NOTE("backoff dissect"); dp = backref(m, m->coldp, endp, gf, gl, (sopno)0, 0); } assert(dp == NULL || dp == endp); if (dp != NULL) /* found a shorter one */ { break; } /* despite initial appearances, there is no match here */ NOTE("false alarm"); if (m->coldp == stop) { break; } start = m->coldp + 1; /* recycle starting later */ } /* fill in the details if requested */ if (nmatch > 0) { pmatch[0].rm_so = m->coldp - m->offp; pmatch[0].rm_eo = endp - m->offp; } if (nmatch > 1) { assert(m->pmatch != NULL); for (i = 1; i < nmatch; i++) { if (i <= m->g->nsub) { pmatch[i] = m->pmatch[i]; } else { pmatch[i].rm_so = -1; pmatch[i].rm_eo = -1; } } } free(m->pmatch); free(m->lastpos); STATETEARDOWN(m); return 0; } /* * - dissect - figure out what matched what, no back references */ static const char * /* == stop (success) always */ dissect(struct match *m, const char *start, const char *stop, sopno startst, sopno stopst) { int i; sopno ss; /* start sop of current subRE */ sopno es; /* end sop of current subRE */ const char *sp; /* start of string matched by it */ const char *stp; /* string matched by it cannot pass here */ const char *rest; /* start of rest of string */ const char *tail; /* string unmatched by rest of RE */ sopno ssub; /* start sop of subsubRE */ sopno esub; /* end sop of subsubRE */ const char *ssp; /* start of string matched by subsubRE */ const char *sep; /* end of string matched by subsubRE */ const char *oldssp; /* previous ssp */ const char *dp; AT("diss", start, stop, startst, stopst); sp = start; for (ss = startst; ss < stopst; ss = es) { /* identify end of subRE */ es = ss; switch (OP(m->g->strip[es])) { case OPLUS_: case OQUEST_: es += OPND(m->g->strip[es]); break; case OCH_: while (OP(m->g->strip[es]) != O_CH) { es += OPND(m->g->strip[es]); } break; } es++; /* figure out what it matched */ switch (OP(m->g->strip[ss])) { case OEND: assert(nope); break; case OCHAR: sp++; break; case OBOL: case OEOL: case OBOW: case OEOW: break; case OANY: case OANYOF: sp++; break; case OBACK_: case O_BACK: assert(nope); break; /* cases where length of match is hard to find */ case OQUEST_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) { break; /* yes! */ } /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = es - 1; /* did innards match? */ if (slow(m, sp, rest, ssub, esub) != NULL) { dp = dissect(m, sp, rest, ssub, esub); (void)dp; assert(dp == rest); } else /* no */ { assert(sp == rest); } sp = rest; break; case OPLUS_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) { break; /* yes! */ } /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = es - 1; ssp = sp; oldssp = ssp; for (;;) { /* find last match of innards */ sep = slow(m, ssp, rest, ssub, esub); if (sep == NULL || sep == ssp) { break; /* failed or matched null */ } oldssp = ssp; /* on to next try */ ssp = sep; } if (sep == NULL) { /* last successful match */ sep = ssp; ssp = oldssp; } assert(sep == rest); /* must exhaust substring */ assert(slow(m, ssp, sep, ssub, esub) == rest); dp = dissect(m, ssp, sep, ssub, esub); (void)dp; assert(dp == sep); sp = rest; break; case OCH_: stp = stop; for (;;) { /* how long could this one be? */ rest = slow(m, sp, stp, ss, es); assert(rest != NULL); /* it did match */ /* could the rest match the rest? */ tail = slow(m, rest, stop, es, stopst); if (tail == stop) { break; /* yes! */ } /* no -- try a shorter match for this one */ stp = rest - 1; assert(stp >= sp); /* it did work */ } ssub = ss + 1; esub = ss + OPND(m->g->strip[ss]) - 1; assert(OP(m->g->strip[esub]) == OOR1); for (;;) { /* find first matching branch */ if (slow(m, sp, rest, ssub, esub) == rest) { break; /* it matched all of it */ } /* that one missed, try next one */ assert(OP(m->g->strip[esub]) == OOR1); esub++; assert(OP(m->g->strip[esub]) == OOR2); ssub = esub + 1; esub += OPND(m->g->strip[esub]); if (OP(m->g->strip[esub]) == OOR2) { esub--; } else { assert(OP(m->g->strip[esub]) == O_CH); } } dp = dissect(m, sp, rest, ssub, esub); assert(dp == rest); sp = rest; break; case O_PLUS: case O_QUEST: case OOR1: case OOR2: case O_CH: assert(nope); break; case OLPAREN: i = OPND(m->g->strip[ss]); assert(0 < i && i <= m->g->nsub); m->pmatch[i].rm_so = sp - m->offp; break; case ORPAREN: i = OPND(m->g->strip[ss]); assert(0 < i && i <= m->g->nsub); m->pmatch[i].rm_eo = sp - m->offp; break; default: /* uh oh */ assert(nope); break; } } assert(sp == stop); return sp; } /* * - backref - figure out what matched what, figuring in back references */ static const char * /* == stop (success) or NULL (failure) */ backref(struct match *m, const char *start, const char *stop, sopno startst, sopno stopst, sopno lev, int rec) /* PLUS nesting level */ { int i; sopno ss; /* start sop of current subRE */ const char *sp; /* start of string matched by it */ sopno ssub; /* start sop of subsubRE */ sopno esub; /* end sop of subsubRE */ const char *ssp; /* start of string matched by subsubRE */ const char *dp; size_t len; int hard; sop s; regoff_t offsave; cset *cs; AT("back", start, stop, startst, stopst); sp = start; /* get as far as we can with easy stuff */ hard = 0; for (ss = startst; !hard && ss < stopst; ss++) { switch (OP(s = m->g->strip[ss])) { case OCHAR: if (sp == stop || *sp++ != (char)OPND(s)) { return NULL; } break; case OANY: if (sp == stop) { return NULL; } sp++; break; case OANYOF: cs = &m->g->sets[OPND(s)]; if (sp == stop || !CHIN(cs, *sp++)) { return NULL; } break; case OBOL: if (( sp == m->beginp && !( m->eflags & REG_NOTBOL )) || ( sp > m->offp && sp < m->endp && *( sp - 1 ) == '\n' && ( m->g->cflags & REG_NEWLINE ))) { /* yes */ } else { return NULL; } break; case OEOL: if (( sp == m->endp && !( m->eflags & REG_NOTEOL )) || ( sp < m->endp && *sp == '\n' && ( m->g->cflags & REG_NEWLINE ))) { /* yes */ } else { return NULL; } break; case OBOW: if (sp < m->endp && ISWORD(*sp) && (( sp == m->beginp && !( m->eflags & REG_NOTBOL )) || ( sp > m->offp && !ISWORD(*( sp - 1 ))))) { /* yes */ } else { return NULL; } break; case OEOW: if ((( sp == m->endp && !( m->eflags & REG_NOTEOL )) || ( sp < m->endp && *sp == '\n' && ( m->g->cflags & REG_NEWLINE )) || ( sp < m->endp && !ISWORD(*sp))) && ( sp > m->beginp && ISWORD(*( sp - 1 )))) { /* yes */ } else { return NULL; } break; case O_QUEST: break; case OOR1: /* matches null but needs to skip */ ss++; s = m->g->strip[ss]; do { assert(OP(s) == OOR2); ss += OPND(s); } while ( OP(s = m->g->strip[ss]) != O_CH ); /* note that the ss++ gets us past the O_CH */ break; default: /* have to make a choice */ hard = 1; break; } } if (!hard) { /* that was it! */ if (sp != stop) { return NULL; } return sp; } ss--; /* adjust for the for's final increment */ /* the hard stuff */ AT("hard", sp, stop, ss, stopst); s = m->g->strip[ss]; switch (OP(s)) { case OBACK_: /* the vilest depths */ i = OPND(s); assert(0 < i && i <= m->g->nsub); if (m->pmatch[i].rm_eo == -1) { return NULL; } assert(m->pmatch[i].rm_so != -1); len = m->pmatch[i].rm_eo - m->pmatch[i].rm_so; if (len == 0 && rec++ > MAX_RECURSION) { return NULL; } assert(stop - m->beginp >= len); if (sp > stop - len) { return NULL; /* not enough left to match */ } ssp = m->offp + m->pmatch[i].rm_so; if (memcmp(sp, ssp, len) != 0) { return NULL; } while (m->g->strip[ss] != SOP(O_BACK, i)) { ss++; } return backref(m, sp + len, stop, ss + 1, stopst, lev, rec); break; case OQUEST_: /* to null or not */ dp = backref(m, sp, stop, ss + 1, stopst, lev, rec); if (dp != NULL) { return dp; /* not */ } return backref(m, sp, stop, ss + OPND(s) + 1, stopst, lev, rec); break; case OPLUS_: assert(m->lastpos != NULL); assert(lev + 1 <= m->g->nplus); m->lastpos[lev + 1] = sp; return backref(m, sp, stop, ss + 1, stopst, lev + 1, rec); break; case O_PLUS: if (sp == m->lastpos[lev]) /* last pass matched null */ { return backref(m, sp, stop, ss + 1, stopst, lev - 1, rec); } /* try another pass */ m->lastpos[lev] = sp; dp = backref(m, sp, stop, ss - OPND(s) + 1, stopst, lev, rec); if (dp == NULL) { return backref(m, sp, stop, ss + 1, stopst, lev - 1, rec); } else { return dp; } break; case OCH_: /* find the right one, if any */ ssub = ss + 1; esub = ss + OPND(s) - 1; assert(OP(m->g->strip[esub]) == OOR1); for (;;) { /* find first matching branch */ dp = backref(m, sp, stop, ssub, esub, lev, rec); if (dp != NULL) { return dp; } /* that one missed, try next one */ if (OP(m->g->strip[esub]) == O_CH) { return NULL; /* there is none */ } esub++; assert(OP(m->g->strip[esub]) == OOR2); ssub = esub + 1; esub += OPND(m->g->strip[esub]); if (OP(m->g->strip[esub]) == OOR2) { esub--; } else { assert(OP(m->g->strip[esub]) == O_CH); } } break; case OLPAREN: /* must undo assignment if rest fails */ i = OPND(s); assert(0 < i && i <= m->g->nsub); offsave = m->pmatch[i].rm_so; m->pmatch[i].rm_so = sp - m->offp; dp = backref(m, sp, stop, ss + 1, stopst, lev, rec); if (dp != NULL) { return dp; } m->pmatch[i].rm_so = offsave; return NULL; break; case ORPAREN: /* must undo assignment if rest fails */ i = OPND(s); assert(0 < i && i <= m->g->nsub); offsave = m->pmatch[i].rm_eo; m->pmatch[i].rm_eo = sp - m->offp; dp = backref(m, sp, stop, ss + 1, stopst, lev, rec); if (dp != NULL) { return dp; } m->pmatch[i].rm_eo = offsave; return NULL; break; default: /* uh oh */ assert(nope); break; } /* "can't happen" */ assert(nope); /* NOTREACHED */ return NULL; } /* * - fast - step through the string at top speed */ static const char * /* where tentative match ended, or NULL */ fast(struct match *m, const char *start, const char *stop, sopno startst, sopno stopst) { states st = m->st; states fresh = m->fresh; states tmp = m->tmp; const char *p = start; int c; int lastc; /* previous c */ int flagch; int i; const char *coldp; /* last p after which no match was underway */ if (start == m->offp || ( start == m->beginp && !( m->eflags & REG_NOTBOL ))) { c = OUT; } else { c = *( start - 1 ); } CLEAR(st); SET1(st, startst); st = step(m->g, startst, stopst, st, NOTHING, st); ASSIGN(fresh, st); SP("start", st, *p); coldp = NULL; for (;;) { /* next character */ lastc = c; c = ( p == m->endp ) ? OUT : *p; if (EQ(st, fresh)) { coldp = p; } /* is there an EOL and/or BOL between lastc and c? */ flagch = '\0'; i = 0; if (( lastc == '\n' && m->g->cflags & REG_NEWLINE ) || ( lastc == OUT && !( m->eflags & REG_NOTBOL ))) { flagch = BOL; i = m->g->nbol; } if (( c == '\n' && m->g->cflags & REG_NEWLINE ) || ( c == OUT && !( m->eflags & REG_NOTEOL ))) { flagch = ( flagch == BOL ) ? BOLEOL : EOL; i += m->g->neol; } if (i != 0) { for (; i > 0; i--) { st = step(m->g, startst, stopst, st, flagch, st); } SP("boleol", st, c); } /* how about a word boundary? */ if (( flagch == BOL || ( lastc != OUT && !ISWORD(lastc))) && ( c != OUT && ISWORD(c))) { flagch = BOW; } if (( lastc != OUT && ISWORD(lastc)) && ( flagch == EOL || ( c != OUT && !ISWORD(c)))) { flagch = EOW; } if (flagch == BOW || flagch == EOW) { st = step(m->g, startst, stopst, st, flagch, st); SP("boweow", st, c); } /* are we done? */ if (ISSET(st, stopst) || p == stop) { break; /* NOTE BREAK OUT */ } /* no, we must deal with this character */ ASSIGN(tmp, st); ASSIGN(st, fresh); assert(c != OUT); st = step(m->g, startst, stopst, tmp, c, st); SP("aft", st, c); assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st)); p++; } assert(coldp != NULL); m->coldp = coldp; if (ISSET(st, stopst)) { return p + 1; } else { return NULL; } } /* * - slow - step through the string more deliberately */ static const char * /* where it ended */ slow(struct match *m, const char *start, const char *stop, sopno startst, sopno stopst) { states st = m->st; states empty = m->empty; states tmp = m->tmp; const char *p = start; int c; int lastc; /* previous c */ int flagch; int i; const char *matchp; /* last p at which a match ended */ if (start == m->offp || ( start == m->beginp && !( m->eflags & REG_NOTBOL ))) { c = OUT; } else { c = *( start - 1 ); } AT("slow", start, stop, startst, stopst); CLEAR(st); SET1(st, startst); SP("sstart", st, *p); st = step(m->g, startst, stopst, st, NOTHING, st); matchp = NULL; for (;;) { /* next character */ lastc = c; c = ( p == m->endp ) ? OUT : *p; /* is there an EOL and/or BOL between lastc and c? */ flagch = '\0'; i = 0; if (( lastc == '\n' && m->g->cflags & REG_NEWLINE ) || ( lastc == OUT && !( m->eflags & REG_NOTBOL ))) { flagch = BOL; i = m->g->nbol; } if (( c == '\n' && m->g->cflags & REG_NEWLINE ) || ( c == OUT && !( m->eflags & REG_NOTEOL ))) { flagch = ( flagch == BOL ) ? BOLEOL : EOL; i += m->g->neol; } if (i != 0) { for (; i > 0; i--) { st = step(m->g, startst, stopst, st, flagch, st); } SP("sboleol", st, c); } /* how about a word boundary? */ if (( flagch == BOL || ( lastc != OUT && !ISWORD(lastc))) && ( c != OUT && ISWORD(c))) { flagch = BOW; } if (( lastc != OUT && ISWORD(lastc)) && ( flagch == EOL || ( c != OUT && !ISWORD(c)))) { flagch = EOW; } if (flagch == BOW || flagch == EOW) { st = step(m->g, startst, stopst, st, flagch, st); SP("sboweow", st, c); } /* are we done? */ if (ISSET(st, stopst)) { matchp = p; } if (EQ(st, empty) || p == stop) { break; /* NOTE BREAK OUT */ } /* no, we must deal with this character */ ASSIGN(tmp, st); ASSIGN(st, empty); assert(c != OUT); st = step(m->g, startst, stopst, tmp, c, st); SP("saft", st, c); assert(EQ(step(m->g, startst, stopst, st, NOTHING, st), st)); p++; } return matchp; } /* * - step - map set of states reachable before char to set reachable after */ static states step(struct re_guts *g, sopno start, /* start state within strip */ sopno stop, /* state after stop state within strip */ states bef, /* states reachable before */ int ch, /* character or NONCHAR code */ states aft) /* states already known reachable after */ { cset *cs; sop s; sopno pc; onestate here; /* note, macros know this name */ sopno look; int i; for (pc = start, INIT(here, pc); pc != stop; pc++, INC(here)) { s = g->strip[pc]; switch (OP(s)) { case OEND: assert(pc == stop - 1); break; case OCHAR: /* only characters can match */ assert(!NONCHAR(ch) || ch != (char)OPND(s)); if (ch == (char)OPND(s)) { FWD(aft, bef, 1); } break; case OBOL: if (ch == BOL || ch == BOLEOL) { FWD(aft, bef, 1); } break; case OEOL: if (ch == EOL || ch == BOLEOL) { FWD(aft, bef, 1); } break; case OBOW: if (ch == BOW) { FWD(aft, bef, 1); } break; case OEOW: if (ch == EOW) { FWD(aft, bef, 1); } break; case OANY: if (!NONCHAR(ch)) { FWD(aft, bef, 1); } break; case OANYOF: cs = &g->sets[OPND(s)]; if (!NONCHAR(ch) && CHIN(cs, ch)) { FWD(aft, bef, 1); } break; case OBACK_: /* ignored here */ case O_BACK: FWD(aft, aft, 1); break; case OPLUS_: /* forward, this is just an empty */ FWD(aft, aft, 1); break; case O_PLUS: /* both forward and back */ FWD(aft, aft, 1); i = ISSETBACK(aft, OPND(s)); BACK(aft, aft, OPND(s)); if (!i && ISSETBACK(aft, OPND(s))) { /* oho, must reconsider loop body */ pc -= OPND(s) + 1; INIT(here, pc); } break; case OQUEST_: /* two branches, both forward */ FWD(aft, aft, 1); FWD(aft, aft, OPND(s)); break; case O_QUEST: /* just an empty */ FWD(aft, aft, 1); break; case OLPAREN: /* not significant here */ case ORPAREN: FWD(aft, aft, 1); break; case OCH_: /* mark the first two branches */ FWD(aft, aft, 1); assert(OP(g->strip[pc + OPND(s)]) == OOR2); FWD(aft, aft, OPND(s)); break; case OOR1: /* done a branch, find the O_CH */ if (ISSTATEIN(aft, here)) { for (look = 1; OP(s = g->strip[pc + look]) != O_CH; look += OPND(s)) { assert(OP(s) == OOR2); } FWD(aft, aft, look + 1); } break; case OOR2: /* propagate OCH_'s marking */ FWD(aft, aft, 1); if (OP(g->strip[pc + OPND(s)]) != O_CH) { assert(OP(g->strip[pc + OPND(s)]) == OOR2); FWD(aft, aft, OPND(s)); } break; case O_CH: /* just empty */ FWD(aft, aft, 1); break; default: /* ooooops... */ assert(nope); break; } } return aft; } #ifdef REDEBUG /* * - print - print a set of states */ static void print(struct match *m, const char *caption, states st, int ch, FILE *d) { struct re_guts *g = m->g; int i; int first = 1; if (!( m->eflags & REG_TRACE )) { return; } (void)fprintf(d, "%s", caption); (void)fprintf(d, " %s", pchar(ch)); for (i = 0; i < g->nstates; i++) { if (ISSET(st, i)) { (void)fprintf(d, "%s%d", ( first ) ? "\t" : ", ", i); first = 0; } } (void)fprintf(d, "\n"); } /* * - at - print current situation */ static void at(struct match *m, const char *title, const char *start, const char *stop, sopno startst, sopno stopst) { if (!( m->eflags & REG_TRACE )) { return; } (void)printf("%s %s-", title, pchar(*start)); (void)printf("%s ", pchar(*stop)); (void)printf("%ld-%ld\n", (long)startst, (long)stopst); } # ifndef PCHARDONE # define PCHARDONE /* never again */ static const char *nonchars[] = { "OUT", "BOL", "EOL", "BOLEOL", "NOTHING", "BOW", "EOW" }; # define PNONCHAR(c) \ (( c ) - OUT < ( sizeof ( nonchars ) / sizeof ( nonchars[0] )) \ ? nonchars[( c ) - OUT] : "invalid" ) /* * - pchar - make a character printable * * Is this identical to regchar() over in debug.c? Well, yes. But a * duplicate here avoids having a debugging-capable regexec.o tied to * a matching debug.o, and this is convenient. It all disappears in * the non-debug compilation anyway, so it doesn't matter much. */ static const char * /* -> representation */ pchar(int ch) { static char pbuf[10]; if (NONCHAR(ch)) { if (ch - OUT < ( sizeof ( nonchars ) / sizeof ( nonchars[0] ))) { return nonchars[ch - OUT]; } return "invalid"; } if (isprint((unsigned char)ch) || ch == ' ') { (void)snprintf(pbuf, sizeof pbuf, "%c", ch); } else { (void)snprintf(pbuf, sizeof pbuf, "\\%o", ch); } return pbuf; } # endif /* ifndef PCHARDONE */ #endif /* ifdef REDEBUG */ #undef matcher #undef fast #undef slow #undef dissect #undef backref #undef step #undef print #undef at #undef match #undef nope ================================================ FILE: regex/regcomp.c ================================================ /* $OpenBSD: regcomp.c,v 1.44 2022/12/27 17:10:06 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)regcomp.c 8.5 (Berkeley) 3/20/94 */ #include "../include/compat.h" #include #include #include #include #include #include #include #include #include "utils.h" #include "bsd_regex2.h" #include "cclass.h" #include "cname.h" /* * parse structure, passed up and down to avoid global variables and * other clumsinesses */ struct parse { const char *next; /* next character in RE */ const char *end; /* end of string (-> NUL normally) */ int error; /* has an error been seen? */ sop *strip; /* malloced strip */ sopno ssize; /* malloced strip size (allocated) */ sopno slen; /* malloced strip length (used) */ int ncsalloc; /* number of csets allocated */ struct re_guts *g; #define NPAREN 10 /* we need to remember () 1-9 for back refs */ sopno pbegin[NPAREN]; /* -> ( ([0] unused) */ sopno pend[NPAREN]; /* -> ) ([0] unused) */ }; static void p_ere(struct parse *, int); static void p_ere_exp(struct parse *); static void p_str(struct parse *); static void p_bre(struct parse *, int, int); static int p_simp_re(struct parse *, int); static int p_count(struct parse *); static void p_bracket(struct parse *); static void p_b_term(struct parse *, cset *); static void p_b_cclass(struct parse *, cset *); static void p_b_eclass(struct parse *, cset *); static char p_b_symbol(struct parse *); static char p_b_coll_elem(struct parse *, int); static char othercase(int); static void bothcases(struct parse *, int); static void ordinary(struct parse *, int); static void backslash(struct parse *, int); static void nonnewline(struct parse *); static void repeat(struct parse *, sopno, int, int); static void seterr(struct parse *, int); static cset *allocset(struct parse *); static void freeset(struct parse *, cset *); static int freezeset(struct parse *, cset *); static int firstch(struct parse *, cset *); static int nch(struct parse *, cset *); static sopno dupl(struct parse *, sopno, sopno); static void doemit(struct parse *, sop, size_t); static void doinsert(struct parse *, sop, size_t, sopno); static void dofwd(struct parse *, sopno, sop); static int enlarge(struct parse *, sopno); static void stripsnug(struct parse *, struct re_guts *); static void findmust(struct parse *, struct re_guts *); static sopno pluscount(struct parse *, struct re_guts *); static char nuls[10]; /* place to point scanner in event of error */ /* * macros for use with parse structure * BEWARE: these know that the parse structure is named `p' !!! */ #define PEEK() ( *p->next ) #define PEEK2() ( *( p->next + 1 )) #define MORE() ( p->end - p->next > 0 ) #define MORE2() ( p->end - p->next > 1 ) #define SEE(c) ( MORE() && PEEK() == ( c )) #define SEETWO(a, b) ( MORE2() && PEEK() == ( a ) && PEEK2() == ( b )) #define EAT(c) (( SEE(c)) ? ( NEXT(), 1 ) : 0 ) #define EATTWO(a, b) (( SEETWO(a, b)) ? ( NEXT2(), 1 ) : 0 ) #define NEXT() ( p->next++ ) #define NEXT2() ( p->next += 2 ) #define NEXTn(n) ( p->next += ( n )) #define GETNEXT() ( *p->next++ ) #define SETERROR(e) seterr(p, ( e )) #define REQUIRE(co, e) \ do \ { \ if (!( co )) \ SETERROR(e); \ } while ( 0 ) #define EMIT(op, sopnd) doemit(p, (sop)( op ), (size_t)( sopnd )) #define INSERT(op, pos) doinsert(p, (sop)( op ), HERE() - ( pos ) + 1, pos) #define AHEAD(pos) dofwd(p, pos, HERE() - ( pos )) #define ASTERN(sop, pos) EMIT(sop, HERE() - pos) #define HERE() ( p->slen ) #define THERE() ( p->slen - 1 ) #define THERETHERE() ( p->slen - 2 ) #define DROP(n) ( p->slen -= ( n )) #ifndef NDEBUG static int never = 0; /* for use in asserts; shuts lint up */ #else /* ifndef NDEBUG */ # define never 0 /* some s have bugs too */ #endif /* ifndef NDEBUG */ /* * - regcomp - interface for parser and compilation */ int /* 0 success, otherwise REG_something */ regcomp(regex_t *preg, const char *pattern, int cflags) { struct parse pa; struct re_guts *g; struct parse *p = &pa; int i; size_t len; #ifdef REDEBUG # define GOODFLAGS(f) ( f ) #else /* ifdef REDEBUG */ # define GOODFLAGS(f) (( f ) & ~REG_DUMP ) #endif /* ifdef REDEBUG */ cflags = GOODFLAGS(cflags); if (( cflags & REG_EXTENDED ) && ( cflags & REG_NOSPEC )) { return REG_INVARG; } if (cflags & REG_PEND) { if (preg->re_endp < pattern) { return REG_INVARG; } len = preg->re_endp - pattern; } else { len = strlen((char *)pattern); } /* do the mallocs early so failure handling is easy */ g = malloc(sizeof ( struct re_guts )); if (g == NULL) { return REG_ESPACE; } p->ssize = len / (size_t)2 * (size_t)3 + (size_t)1; /* ugh */ p->strip = openbsd_reallocarray(NULL, p->ssize, sizeof ( sop )); p->slen = 0; if (p->strip == NULL) { free(g); return REG_ESPACE; } /* set things up */ p->g = g; p->next = pattern; p->end = p->next + len; p->error = 0; p->ncsalloc = 0; for (i = 0; i < NPAREN; i++) { p->pbegin[i] = 0; p->pend[i] = 0; } g->csetsize = NC; g->sets = NULL; g->setbits = NULL; g->ncsets = 0; g->cflags = cflags; g->iflags = 0; g->nbol = 0; g->neol = 0; g->must = NULL; g->mlen = 0; g->nsub = 0; g->backrefs = 0; /* do it */ EMIT(OEND, 0); g->firststate = THERE(); if (cflags & REG_EXTENDED) { p_ere(p, OUT); } else if (cflags & REG_NOSPEC) { p_str(p); } else { p_bre(p, OUT, OUT); } EMIT(OEND, 0); g->laststate = THERE(); /* tidy up loose ends and fill things in */ stripsnug(p, g); findmust(p, g); g->nplus = pluscount(p, g); g->magic = MAGIC2; preg->re_nsub = g->nsub; preg->re_g = g; preg->re_magic = MAGIC1; #ifndef REDEBUG /* not debugging, so can't rely on the assert() in regexec() */ if (g->iflags & BAD) { SETERROR(REG_ASSERT); } #endif /* ifndef REDEBUG */ /* win or lose, we're done */ if (p->error != 0) /* lose */ { regfree(preg); } return p->error; } /* * - p_ere - ERE parser top level, concatenation and alternation */ static void p_ere(struct parse *p, int stop) /* character this ERE should end at */ { char c; sopno prevback; sopno prevfwd; sopno conc; int first = 1; /* is this the first alternative? */ for (;;) { /* do a bunch of concatenated expressions */ conc = HERE(); while (MORE() && ( c = PEEK()) != '|' && c != stop) { p_ere_exp(p); } REQUIRE(HERE() != conc, REG_EMPTY); /* require nonempty */ if (!EAT('|')) { break; /* NOTE BREAK OUT */ } if (first) { INSERT(OCH_, conc); /* offset is wrong */ prevfwd = conc; prevback = conc; first = 0; } ASTERN(OOR1, prevback); prevback = THERE(); AHEAD(prevfwd); /* fix previous offset */ prevfwd = HERE(); EMIT(OOR2, 0); /* offset is very wrong */ } if (!first) { /* tail-end fixups */ AHEAD(prevfwd); ASTERN(O_CH, prevback); } assert(!MORE() || SEE(stop)); } /* * - p_ere_exp - parse one subERE, an atom possibly followed by a repetition op */ static void p_ere_exp(struct parse *p) { char c; sopno pos; int count; int count2; sopno subno; int wascaret = 0; assert(MORE()); /* caller should have ensured this */ c = GETNEXT(); pos = HERE(); switch (c) { case '(': REQUIRE(MORE(), REG_EPAREN); p->g->nsub++; subno = p->g->nsub; if (subno < NPAREN) { p->pbegin[subno] = HERE(); } EMIT(OLPAREN, subno); if (!SEE(')')) { p_ere(p, ')'); } if (subno < NPAREN) { p->pend[subno] = HERE(); assert(p->pend[subno] != 0); } EMIT(ORPAREN, subno); REQUIRE(MORE() && GETNEXT() == ')', REG_EPAREN); break; case '^': EMIT(OBOL, 0); p->g->iflags |= USEBOL; p->g->nbol++; wascaret = 1; break; case '$': EMIT(OEOL, 0); p->g->iflags |= USEEOL; p->g->neol++; break; case '|': SETERROR(REG_EMPTY); break; case '*': case '+': case '?': SETERROR(REG_BADRPT); break; case '.': if (p->g->cflags & REG_NEWLINE) { nonnewline(p); } else { EMIT(OANY, 0); } break; case '[': p_bracket(p); break; case '\\': REQUIRE(MORE(), REG_EESCAPE); c = GETNEXT(); backslash(p, c); break; case '{': /* okay as ordinary except if digit follows */ REQUIRE(!MORE() || !isdigit((uch)PEEK()), REG_BADRPT); /* FALLTHROUGH */ default: if (p->error != 0) { return; } ordinary(p, c); break; } if (!MORE()) { return; } c = PEEK(); /* we call { a repetition if followed by a digit */ if (!( c == '*' || c == '+' || c == '?' || ( c == '{' && MORE2() && isdigit((uch)PEEK2())))) { return; /* no repetition, we're done */ } NEXT(); REQUIRE(!wascaret, REG_BADRPT); switch (c) { case '*': /* implemented as +? */ /* this case does not require the (y|) trick, noKLUDGE */ INSERT(OPLUS_, pos); ASTERN(O_PLUS, pos); INSERT(OQUEST_, pos); ASTERN(O_QUEST, pos); break; case '+': INSERT(OPLUS_, pos); ASTERN(O_PLUS, pos); break; case '?': /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ INSERT(OCH_, pos); /* offset slightly wrong */ ASTERN(OOR1, pos); /* this one's right */ AHEAD(pos); /* fix the OCH_ */ EMIT(OOR2, 0); /* offset very wrong... */ AHEAD(THERE()); /* ...so fix it */ ASTERN(O_CH, THERETHERE()); break; case '{': count = p_count(p); if (EAT(',')) { if (isdigit((uch)PEEK())) { count2 = p_count(p); REQUIRE(count <= count2, REG_BADBR); } else /* single number with comma */ { count2 = INFINITY; } } else /* just a single number */ { count2 = count; } repeat(p, pos, count, count2); if (!EAT('}')) { /* error heuristics */ while (MORE() && PEEK() != '}') { NEXT(); } REQUIRE(MORE(), REG_EBRACE); SETERROR(REG_BADBR); } break; } if (!MORE()) { return; } c = PEEK(); if (!( c == '*' || c == '+' || c == '?' || ( c == '{' && MORE2() && isdigit((uch)PEEK2())))) { return; } SETERROR(REG_BADRPT); } /* * - p_str - string (no metacharacters) "parser" */ static void p_str(struct parse *p) { REQUIRE(MORE(), REG_EMPTY); while (MORE()) { ordinary(p, GETNEXT()); } } /* * - p_bre - BRE parser top level, anchoring and concatenation * Giving end1 as OUT essentially eliminates the end1/end2 check. * * This implementation is a bit of a kludge, in that a trailing $ is first * taken as an ordinary character and then revised to be an anchor. The * only undesirable side effect is that '$' gets included as a character * category in such cases. This is fairly harmless; not worth fixing. * The amount of lookahead needed to avoid this kludge is excessive. */ static void p_bre(struct parse *p, int end1, /* first terminating character */ int end2) /* second terminating character */ { sopno start = HERE(); int first = 1; /* first subexpression? */ int wasdollar = 0; if (EAT('^')) { EMIT(OBOL, 0); p->g->iflags |= USEBOL; p->g->nbol++; } while (MORE() && !SEETWO(end1, end2)) { wasdollar = p_simp_re(p, first); first = 0; } if (wasdollar) { /* oops, that was a trailing anchor */ DROP(1); EMIT(OEOL, 0); p->g->iflags |= USEEOL; p->g->neol++; } REQUIRE(HERE() != start, REG_EMPTY); /* require nonempty */ } /* * - p_simp_re - parse a simple RE, an atom possibly followed by a repetition */ static int /* was the simple RE an un-backslashed $? */ p_simp_re(struct parse *p, int starordinary) /* is a leading * an ordinary character? */ { int c; int count; int count2; sopno pos; int i; sopno subno; #define BACKSL ( 1 << CHAR_BIT ) pos = HERE(); /* repetition op, if any, covers from here */ assert(MORE()); /* caller should have ensured this */ c = GETNEXT(); if (c == '\\') { REQUIRE(MORE(), REG_EESCAPE); c = BACKSL | GETNEXT(); } switch (c) { case '.': if (p->g->cflags & REG_NEWLINE) { nonnewline(p); } else { EMIT(OANY, 0); } break; case '[': p_bracket(p); break; case BACKSL | '<': EMIT(OBOW, 0); break; case BACKSL | '>': EMIT(OEOW, 0); break; case BACKSL | '{': SETERROR(REG_BADRPT); break; case BACKSL | '(': p->g->nsub++; subno = p->g->nsub; if (subno < NPAREN) { p->pbegin[subno] = HERE(); } EMIT(OLPAREN, subno); /* the MORE here is an error heuristic */ if (MORE() && !SEETWO('\\', ')')) { p_bre(p, '\\', ')'); } if (subno < NPAREN) { p->pend[subno] = HERE(); assert(p->pend[subno] != 0); } EMIT(ORPAREN, subno); REQUIRE(EATTWO('\\', ')'), REG_EPAREN); break; case BACKSL | ')': /* should not get here -- must be user */ case BACKSL | '}': SETERROR(REG_EPAREN); break; case BACKSL | '1': case BACKSL | '2': case BACKSL | '3': case BACKSL | '4': case BACKSL | '5': case BACKSL | '6': case BACKSL | '7': case BACKSL | '8': case BACKSL | '9': i = ( c & ~BACKSL ) - '0'; assert(i < NPAREN); if (p->pend[i] != 0) { assert(i <= p->g->nsub); EMIT(OBACK_, i); assert(p->pbegin[i] != 0); assert(OP(p->strip[p->pbegin[i]]) == OLPAREN); assert(OP(p->strip[p->pend[i]]) == ORPAREN); (void)dupl(p, p->pbegin[i] + 1, p->pend[i]); EMIT(O_BACK, i); } else { SETERROR(REG_ESUBREG); } p->g->backrefs = 1; break; case '*': REQUIRE(starordinary, REG_BADRPT); /* FALLTHROUGH */ default: if (p->error != 0) { return 0; /* Definitely not $... */ } ordinary(p, (char)c); break; } if (EAT('*')) { /* implemented as +? */ /* this case does not require the (y|) trick, noKLUDGE */ INSERT(OPLUS_, pos); ASTERN(O_PLUS, pos); INSERT(OQUEST_, pos); ASTERN(O_QUEST, pos); } else if (EATTWO('\\', '{')) { count = p_count(p); if (EAT(',')) { if (MORE() && isdigit((uch)PEEK())) { count2 = p_count(p); REQUIRE(count <= count2, REG_BADBR); } else /* single number with comma */ { count2 = INFINITY; } } else /* just a single number */ { count2 = count; } repeat(p, pos, count, count2); if (!EATTWO('\\', '}')) { /* error heuristics */ while (MORE() && !SEETWO('\\', '}')) { NEXT(); } REQUIRE(MORE(), REG_EBRACE); SETERROR(REG_BADBR); } } else if (c == '$') /* $ (but not \$) ends it */ { return 1; } return 0; } /* * - p_count - parse a repetition count */ static int /* the value */ p_count(struct parse *p) { int count = 0; int ndigits = 0; while (MORE() && isdigit((uch)PEEK()) && count <= DUPMAX) { count = count * 10 + ( GETNEXT() - '0' ); ndigits++; } REQUIRE(ndigits > 0 && count <= DUPMAX, REG_BADBR); return count; } /* * - p_bracket - parse a bracketed character list * * Note a significant property of this code: if the allocset() did SETERROR, * no set operations are done. */ static void p_bracket(struct parse *p) { cset *cs; int invert = 0; /* Dept of Truly Sickening Special-Case Kludges */ if (p->end - p->next > 5) { if (strncmp(p->next, "[:<:]]", 6) == 0) { EMIT(OBOW, 0); NEXTn(6); return; } if (strncmp(p->next, "[:>:]]", 6) == 0) { EMIT(OEOW, 0); NEXTn(6); return; } } if (( cs = allocset(p)) == NULL) { /* allocset did set error status in p */ return; } if (EAT('^')) { invert++; /* make note to invert set at end */ } if (EAT(']')) { CHadd(cs, ']'); } else if (EAT('-')) { CHadd(cs, '-'); } while (MORE() && PEEK() != ']' && !SEETWO('-', ']')) { p_b_term(p, cs); } if (EAT('-')) { CHadd(cs, '-'); } REQUIRE(MORE() && GETNEXT() == ']', REG_EBRACK); if (p->error != 0) { /* don't mess things up further */ freeset(p, cs); return; } if (p->g->cflags & REG_ICASE) { int i; int ci; for (i = p->g->csetsize - 1; i >= 0; i--) { if (CHIN(cs, i) && isalpha(i)) { ci = othercase(i); if (ci != i) { CHadd(cs, ci); } } } } if (invert) { int i; for (i = p->g->csetsize - 1; i >= 0; i--) { if (CHIN(cs, i)) { CHsub(cs, i); } else { CHadd(cs, i); } } if (p->g->cflags & REG_NEWLINE) { CHsub(cs, '\n'); } } if (nch(p, cs) == 1) { /* optimize singleton sets */ ordinary(p, firstch(p, cs)); freeset(p, cs); } else { EMIT(OANYOF, freezeset(p, cs)); } } /* * - p_b_term - parse one term of a bracketed character list */ static void p_b_term(struct parse *p, cset *cs) { char c; char start, finish; int i; /* classify what we've got */ switch (( MORE()) ? PEEK() : '\0') { case '[': c = ( MORE2()) ? PEEK2() : '\0'; break; case '-': SETERROR(REG_ERANGE); return; /* NOTE RETURN */ break; default: c = '\0'; break; } switch (c) { case ':': /* character class */ NEXT2(); REQUIRE(MORE(), REG_EBRACK); c = PEEK(); REQUIRE(c != '-' && c != ']', REG_ECTYPE); p_b_cclass(p, cs); REQUIRE(MORE(), REG_EBRACK); REQUIRE(EATTWO(':', ']'), REG_ECTYPE); break; case '=': /* equivalence class */ NEXT2(); REQUIRE(MORE(), REG_EBRACK); c = PEEK(); REQUIRE(c != '-' && c != ']', REG_ECOLLATE); p_b_eclass(p, cs); REQUIRE(MORE(), REG_EBRACK); REQUIRE(EATTWO('=', ']'), REG_ECOLLATE); break; default: /* symbol, ordinary character, or range */ /* xxx revision needed for multichar stuff */ start = p_b_symbol(p); if (SEE('-') && MORE2() && PEEK2() != ']') { /* range */ NEXT(); if (EAT('-')) { finish = '-'; } else { finish = p_b_symbol(p); } } else { finish = start; } /* xxx what about signed chars here... */ REQUIRE(start <= finish, REG_ERANGE); for (i = start; i <= finish; i++) { CHadd(cs, i); } break; } } /* * - p_b_cclass - parse a character-class name and deal with it */ static void p_b_cclass(struct parse *p, cset *cs) { const char *sp = p->next; const struct cclass *cp; size_t len; const char *u; char c; while (MORE() && isalpha((uch)PEEK())) { NEXT(); } len = p->next - sp; for (cp = cclasses; cp->name != NULL; cp++) { if (strncmp(cp->name, sp, len) == 0 && cp->name[len] == '\0') { break; } } if (cp->name == NULL) { /* oops, didn't find it */ SETERROR(REG_ECTYPE); return; } u = cp->chars; while (( c = *u++ ) != '\0') { CHadd(cs, c); } } /* * - p_b_eclass - parse an equivalence-class name and deal with it * * This implementation is incomplete. xxx */ static void p_b_eclass(struct parse *p, cset *cs) { char c; c = p_b_coll_elem(p, '='); CHadd(cs, c); } /* * - p_b_symbol - parse a character or [..]ed multicharacter collating symbol */ static char /* value of symbol */ p_b_symbol(struct parse *p) { char value; REQUIRE(MORE(), REG_EBRACK); if (!EATTWO('[', '.')) { return GETNEXT(); } /* collating symbol */ value = p_b_coll_elem(p, '.'); REQUIRE(EATTWO('.', ']'), REG_ECOLLATE); return value; } /* * - p_b_coll_elem - parse a collating-element name and look it up */ static char /* value of collating element */ p_b_coll_elem(struct parse *p, int endc) /* name ended by endc,']' */ { const char *sp = p->next; const struct cname *cp; size_t len; while (MORE() && !SEETWO(endc, ']')) { NEXT(); } if (!MORE()) { SETERROR(REG_EBRACK); return 0; } len = p->next - sp; for (cp = cnames; cp->name != NULL; cp++) { if (strncmp(cp->name, sp, len) == 0 && strlen(cp->name) == len) { return cp->code; /* known name */ } } if (len == 1) { return *sp; /* single character */ } SETERROR(REG_ECOLLATE); /* neither */ return 0; } /* * - othercase - return the case counterpart of an alphabetic */ static char /* if no counterpart, return ch */ othercase(int ch) { ch = (uch)ch; assert(isalpha(ch)); if (isupper(ch)) { return (uch)tolower(ch); } else if (islower(ch)) { return (uch)toupper(ch); } else /* peculiar, but could happen */ { return ch; } } /* * - bothcases - emit a dualcase version of a two-case character * * Boy, is this implementation ever a kludge... */ static void bothcases(struct parse *p, int ch) { const char *oldnext = p->next; const char *oldend = p->end; char bracket[3]; ch = (uch)ch; assert(othercase(ch) != ch); /* p_bracket() would recurse */ p->next = bracket; p->end = bracket + 2; bracket[0] = ch; bracket[1] = ']'; bracket[2] = '\0'; p_bracket(p); assert(p->next == bracket + 2); p->next = oldnext; p->end = oldend; } /* * - ordinary - emit an ordinary character */ static void ordinary(struct parse *p, int ch) { if (( p->g->cflags & REG_ICASE ) && isalpha((uch)ch) && othercase(ch) != ch) { bothcases(p, ch); } else { EMIT(OCHAR, (uch)ch); } } /* * do something magic with this character, but only if it's extra magic */ static void backslash(struct parse *p, int ch) { switch (ch) { case '<': EMIT(OBOW, 0); break; case '>': EMIT(OEOW, 0); break; default: ordinary(p, ch); break; } } /* * - nonnewline - emit REG_NEWLINE version of OANY * * Boy, is this implementation ever a kludge... */ static void nonnewline(struct parse *p) { const char *oldnext = p->next; const char *oldend = p->end; static const char bracket[4] = { '^', '\n', ']', '\0' }; p->next = bracket; p->end = bracket + 3; p_bracket(p); assert(p->next == bracket + 3); p->next = oldnext; p->end = oldend; } /* * - repeat - generate code for a bounded repetition, recursively if needed */ static void repeat(struct parse *p, sopno start, /* operand from here to end of strip */ int from, /* repeated from this number */ int to) /* to this number of times (maybe INFINITY) */ { sopno finish = HERE(); #define N 2 #define INF 3 #define REP(f, t) (( f ) * 8 + ( t )) #define MAP(n) ((( n ) <= 1 ) ? ( n ) : (( n ) == INFINITY ) ? INF : N ) sopno copy; if (p->error != 0) /* head off possible runaway recursion */ { return; } assert(from <= to); switch (REP(MAP(from), MAP(to))) { case REP(0, 0): /* must be user doing this */ DROP(finish - start); /* drop the operand */ break; case REP(0, 1): /* as x{1,1}? */ case REP(0, N): /* as x{1,n}? */ case REP(0, INF): /* as x{1,}? */ /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ INSERT(OCH_, start); /* offset is wrong... */ repeat(p, start + 1, 1, to); ASTERN(OOR1, start); AHEAD(start); /* ... fix it */ EMIT(OOR2, 0); AHEAD(THERE()); ASTERN(O_CH, THERETHERE()); break; case REP(1, 1): /* trivial case */ /* done */ break; case REP(1, N): /* as x?x{1,n-1} */ /* KLUDGE: emit y? as (y|) until subtle bug gets fixed */ INSERT(OCH_, start); ASTERN(OOR1, start); AHEAD(start); EMIT(OOR2, 0); /* offset very wrong... */ AHEAD(THERE()); /* ...so fix it */ ASTERN(O_CH, THERETHERE()); copy = dupl(p, start + 1, finish + 1); assert(copy == finish + 4); repeat(p, copy, 1, to - 1); break; case REP(1, INF): /* as x+ */ INSERT(OPLUS_, start); ASTERN(O_PLUS, start); break; case REP(N, N): /* as xx{m-1,n-1} */ copy = dupl(p, start, finish); repeat(p, copy, from - 1, to - 1); break; case REP(N, INF): /* as xx{n-1,INF} */ copy = dupl(p, start, finish); repeat(p, copy, from - 1, to); break; default: /* "can't happen" */ SETERROR(REG_ASSERT); /* just in case */ break; } } /* * - seterr - set an error condition */ static void seterr(struct parse *p, int e) { if (p->error == 0) /* keep earliest error condition */ { p->error = e; } p->next = nuls; /* try to bring things to a halt */ p->end = nuls; } /* * - allocset - allocate a set of characters for [] */ static cset * allocset(struct parse *p) { int no = p->g->ncsets++; size_t nc; size_t nbytes; cset *cs; size_t css = (size_t)p->g->csetsize; int i; if (no >= p->ncsalloc) { /* need another column of space */ void *ptr; p->ncsalloc += CHAR_BIT; nc = p->ncsalloc; assert(nc % CHAR_BIT == 0); ptr = openbsd_reallocarray(p->g->sets, nc, sizeof ( cset )); if (ptr == NULL) { goto nomem; } p->g->sets = ptr; ptr = openbsd_reallocarray(p->g->setbits, nc / CHAR_BIT, css); if (ptr == NULL) { goto nomem; } nbytes = ( nc / CHAR_BIT ) * css; p->g->setbits = ptr; for (i = 0; i < no; i++) { p->g->sets[i].ptr = p->g->setbits + css * ( i / CHAR_BIT ); } (void)memset((char *)p->g->setbits + ( nbytes - css ), 0, css); } /* XXX should not happen */ if (p->g->sets == NULL || p->g->setbits == NULL) { goto nomem; } cs = &p->g->sets[no]; cs->ptr = p->g->setbits + css * (( no ) / CHAR_BIT ); cs->mask = 1 << (( no ) % CHAR_BIT ); cs->hash = 0; return cs; nomem: free(p->g->sets); p->g->sets = NULL; free(p->g->setbits); p->g->setbits = NULL; SETERROR(REG_ESPACE); /* caller's responsibility not to do set ops */ return NULL; } /* * - freeset - free a now-unused set */ static void freeset(struct parse *p, cset *cs) { int i; cset *top = &p->g->sets[p->g->ncsets]; size_t css = (size_t)p->g->csetsize; for (i = 0; i < css; i++) { CHsub(cs, i); } if (cs == top - 1) /* recover only the easy case */ { p->g->ncsets--; } } /* * - freezeset - final processing on a set of characters * * The main task here is merging identical sets. This is usually a waste * of time (although the hash code minimizes the overhead), but can win * big if REG_ICASE is being used. REG_ICASE, by the way, is why the hash * is done using addition rather than xor -- all ASCII [aA] sets xor to * the same value! */ static int /* set number */ freezeset(struct parse *p, cset *cs) { uch h = cs->hash; int i; cset *top = &p->g->sets[p->g->ncsets]; cset *cs2; size_t css = (size_t)p->g->csetsize; /* look for an earlier one which is the same */ for (cs2 = &p->g->sets[0]; cs2 < top; cs2++) { if (cs2->hash == h && cs2 != cs) { /* maybe */ for (i = 0; i < css; i++) { if (CHIN(cs2, i) != CHIN(cs, i)) { break; /* no */ } } if (i == css) { break; /* yes */ } } } if (cs2 < top) { /* found one */ freeset(p, cs); cs = cs2; } return (int)( cs - p->g->sets ); } /* * - firstch - return first character in a set (which must have at least one) */ static int /* character; there is no "none" value */ firstch(struct parse *p, cset *cs) { int i; size_t css = (size_t)p->g->csetsize; for (i = 0; i < css; i++) { if (CHIN(cs, i)) { return (char)i; } } assert(never); return 0; /* arbitrary */ } /* * - nch - number of characters in a set */ static int nch(struct parse *p, cset *cs) { int i; size_t css = (size_t)p->g->csetsize; int n = 0; for (i = 0; i < css; i++) { if (CHIN(cs, i)) { n++; } } return n; } /* * - dupl - emit a duplicate of a bunch of sops */ static sopno /* start of duplicate */ dupl(struct parse *p, sopno start, /* from here */ sopno finish) /* to this less one */ { sopno ret = HERE(); sopno len = finish - start; assert(finish >= start); if (len == 0) { return ret; } if (!enlarge(p, p->ssize + len)) /* this many unexpected additions */ { return ret; } (void)memcpy(p->strip + p->slen, p->strip + start, len * sizeof ( sop )); p->slen += len; return ret; } /* * - doemit - emit a strip operator * * It might seem better to implement this as a macro with a function as * hard-case backup, but it's just too big and messy unless there are * some changes to the data structures. Maybe later. */ static void doemit(struct parse *p, sop op, size_t opnd) { /* avoid making error situations worse */ if (p->error != 0) { return; } /* deal with oversize operands ("can't happen", more or less) */ assert(opnd < 1 << OPSHIFT); /* deal with undersized strip */ if (p->slen >= p->ssize) { if (!enlarge(p, ( p->ssize + 1 ) / 2 * 3)) /* +50% */ { return; } } /* finally, it's all reduced to the easy case */ p->strip[p->slen++] = SOP(op, opnd); } /* * - doinsert - insert a sop into the strip */ static void doinsert(struct parse *p, sop op, size_t opnd, sopno pos) { sopno sn; sop s; int i; /* avoid making error situations worse */ if (p->error != 0) { return; } sn = HERE(); EMIT(op, opnd); /* do checks, ensure space */ assert(HERE() == sn + 1); s = p->strip[sn]; /* adjust paren pointers */ assert(pos > 0); for (i = 1; i < NPAREN; i++) { if (p->pbegin[i] >= pos) { p->pbegin[i]++; } if (p->pend[i] >= pos) { p->pend[i]++; } } memmove( (char *)&p->strip[pos + 1], (char *)&p->strip[pos], ( HERE() - pos - 1 ) * sizeof ( sop )); p->strip[pos] = s; } /* * - dofwd - complete a forward reference */ static void dofwd(struct parse *p, sopno pos, sop value) { /* avoid making error situations worse */ if (p->error != 0) { return; } assert(value < 1 << OPSHIFT); p->strip[pos] = OP(p->strip[pos]) | value; } /* * - enlarge - enlarge the strip */ static int enlarge(struct parse *p, sopno size) { sop *sp; if (p->ssize >= size) { return 1; } sp = openbsd_reallocarray(p->strip, size, sizeof ( sop )); if (sp == NULL) { SETERROR(REG_ESPACE); return 0; } p->strip = sp; p->ssize = size; return 1; } /* * - stripsnug - compact the strip */ static void stripsnug(struct parse *p, struct re_guts *g) { g->nstates = p->slen; g->strip = openbsd_reallocarray(p->strip, p->slen, sizeof ( sop )); if (g->strip == NULL) { SETERROR(REG_ESPACE); g->strip = p->strip; } } /* * - findmust - fill in must and mlen with longest mandatory literal string * * This algorithm could do fancy things like analyzing the operands of | * for common subsequences. Someday. This code is simple and finds most * of the interesting cases. * * Note that must and mlen got initialized during setup. */ static void findmust(struct parse *p, struct re_guts *g) { sop *scan; sop *start; /* start initialized in the default case, after that */ sop *newstart; /* newstart was initialized in the OCHAR case */ sopno newlen; sop s; char *cp; sopno i; /* avoid making error situations worse */ if (p->error != 0) { return; } /* find the longest OCHAR sequence in strip */ newlen = 0; scan = g->strip + 1; do { s = *scan++; switch (OP(s)) { case OCHAR: /* sequence member */ if (newlen == 0) /* new sequence */ { newstart = scan - 1; } newlen++; break; case OPLUS_: /* things that don't break one */ case OLPAREN: case ORPAREN: break; case OQUEST_: /* things that must be skipped */ case OCH_: scan--; do { scan += OPND(s); s = *scan; /* assert() interferes w debug printouts */ if (OP(s) != O_QUEST && OP(s) != O_CH && OP(s) != OOR2) { g->iflags |= BAD; return; } } while ( OP(s) != O_QUEST && OP(s) != O_CH ); /* fallthrough */ default: /* things that break a sequence */ if (newlen > g->mlen) { /* ends one */ start = newstart; g->mlen = newlen; } newlen = 0; break; } } while ( OP(s) != OEND ); if (g->mlen == 0) /* there isn't one */ { return; } /* turn it into a character string */ g->must = malloc((size_t)g->mlen + 1); if (g->must == NULL) { /* argh; just forget it */ g->mlen = 0; return; } cp = g->must; scan = start; for (i = g->mlen; i > 0; i--) { while (OP(s = *scan++) != OCHAR) { continue; } assert(cp < g->must + g->mlen); *cp++ = (char)OPND(s); } assert(cp == g->must + g->mlen); *cp = '\0'; /* just on general principles */ } /* * - pluscount - count + nesting */ static sopno /* nesting depth */ pluscount(struct parse *p, struct re_guts *g) { sop *scan; sop s; sopno plusnest = 0; sopno maxnest = 0; if (p->error != 0) { return 0; /* there may not be an OEND */ } scan = g->strip + 1; do { s = *scan++; switch (OP(s)) { case OPLUS_: plusnest++; break; case O_PLUS: if (plusnest > maxnest) { maxnest = plusnest; } plusnest--; break; } } while ( OP(s) != OEND ); if (plusnest != 0) { g->iflags |= BAD; } return maxnest; } ================================================ FILE: regex/regerror.c ================================================ /* $OpenBSD: regerror.c,v 1.15 2020/12/30 08:56:38 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)regerror.c 8.4 (Berkeley) 3/20/94 */ #include #include #include #include #include #include #include #include #include "utils.h" static const char *regatoi(const regex_t *, char *, int); static const struct rerr { int code; const char *name; const char *explain; } rerrs[] = { { REG_NOMATCH, "REG_NOMATCH", "regexec() failed to match" }, { REG_BADPAT, "REG_BADPAT", "invalid regular expression" }, { REG_ECOLLATE, "REG_ECOLLATE", "invalid collating element" }, { REG_ECTYPE, "REG_ECTYPE", "invalid character class" }, { REG_EESCAPE, "REG_EESCAPE", "trailing backslash (\\)" }, { REG_ESUBREG, "REG_ESUBREG", "invalid backreference number" }, { REG_EBRACK, "REG_EBRACK", "brackets ([ ]) not balanced" }, { REG_EPAREN, "REG_EPAREN", "parentheses not balanced" }, { REG_EBRACE, "REG_EBRACE", "braces not balanced" }, { REG_BADBR, "REG_BADBR", "invalid repetition count(s)" }, { REG_ERANGE, "REG_ERANGE", "invalid character range" }, { REG_ESPACE, "REG_ESPACE", "out of memory" }, { REG_BADRPT, "REG_BADRPT", "repetition-operator operand invalid" }, { REG_EMPTY, "REG_EMPTY", "empty (sub)expression" }, { REG_ASSERT, "REG_ASSERT", "\"can't happen\" -- you found a bug" }, { REG_INVARG, "REG_INVARG", "invalid argument to regex routine" }, { 0, "", "*** unknown regexp error code ***" } }; /* * - regerror - the interface to error numbers * = extern size_t regerror(int, const regex_t *, char *, size_t); */ size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) { const struct rerr *r; size_t len; int target = errcode & ~REG_ITOA; const char *s; char convbuf[50]; if (errcode == REG_ATOI) { s = regatoi(preg, convbuf, sizeof convbuf); } else { for (r = rerrs; r->code != 0; r++) { if (r->code == target) { break; } } if (errcode & REG_ITOA) { if (r->code != 0) { assert(strlen(r->name) < sizeof ( convbuf )); (void)openbsd_strlcpy(convbuf, r->name, sizeof convbuf); } else { (void)snprintf(convbuf, sizeof convbuf, "REG_0x%x", target); } s = convbuf; } else { s = r->explain; } } if (errbuf_size != 0) { len = openbsd_strlcpy(errbuf, s, errbuf_size); } else { len = strlen(s); } return len + 1; } /* * - regatoi - internal routine to implement REG_ATOI */ static const char * regatoi(const regex_t *preg, char *localbuf, int localbufsize) { const struct rerr *r; for (r = rerrs; r->code != 0; r++) { if (strcmp(r->name, preg->re_endp) == 0) { break; } } if (r->code == 0) { return "0"; } (void)snprintf(localbuf, localbufsize, "%d", r->code); return localbuf; } ================================================ FILE: regex/regexec.c ================================================ /* $OpenBSD: regexec.c,v 1.14 2018/07/11 12:38:46 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)regexec.c 8.3 (Berkeley) 3/20/94 */ /* * the outer shell of regexec() * * This file includes engine.c *twice*, after muchos fiddling with the * macros that code uses. This lets the same code operate on two different * representations for state sets. */ #include #include #include #include #include #include #include #include "utils.h" #include "bsd_regex2.h" /* macros for manipulating states, small version */ #define states long #define states1 long /* for later use in regexec() decision */ #define CLEAR(v) (( v ) = 0 ) #define SET0(v, n) (( v ) &= ~((unsigned long)1 << ( n ))) #define SET1(v, n) (( v ) |= (unsigned long)1 << ( n )) #define ISSET(v, n) ((( v ) & ((unsigned long)1 << ( n ))) != 0 ) #define ASSIGN(d, s) (( d ) = ( s )) #define EQ(a, b) (( a ) == ( b )) #define STATEVARS long dummy /* dummy version */ #define STATESETUP(m, n) /* nothing */ #define STATETEARDOWN(m) /* nothing */ #define SETUP(v) (( v ) = 0 ) #define onestate long #define INIT(o, n) (( o ) = (unsigned long)1 << ( n )) #define INC(o) (( o ) <<= 1 ) #define ISSTATEIN(v, o) ((( v ) & ( o )) != 0 ) /* some abbreviations; note that some of these know variable names! */ /* do "if I'm here, I can also be there" etc without branches */ #define FWD(dst, src, n) (( dst ) |= ((unsigned long)( src ) & ( here )) << ( n )) #define BACK(dst, src, n) (( dst ) |= ((unsigned long)( src ) & ( here )) >> ( n )) #define ISSETBACK(v, n) ((( v ) & ((unsigned long)here >> ( n ))) != 0 ) /* function names */ #define SNAMES /* engine.c looks after details */ #include "engine.c" /* now undo things */ #undef states #undef CLEAR #undef SET0 #undef SET1 #undef ISSET #undef ASSIGN #undef EQ #undef STATEVARS #undef STATESETUP #undef STATETEARDOWN #undef SETUP #undef onestate #undef INIT #undef INC #undef ISSTATEIN #undef FWD #undef BACK #undef ISSETBACK #undef SNAMES /* macros for manipulating states, large version */ #define states char * #define CLEAR(v) memset(v, 0, m->g->nstates) #define SET0(v, n) (( v )[n] = 0 ) #define SET1(v, n) (( v )[n] = 1 ) #define ISSET(v, n) (( v )[n] ) #define ASSIGN(d, s) memcpy(d, s, m->g->nstates) #define EQ(a, b) ( memcmp(a, b, m->g->nstates) == 0 ) #define STATEVARS \ long vn; \ char *space #define STATESETUP(m, nv) \ { \ ( m )->space = openbsd_reallocarray( \ NULL, ( m )->g->nstates, ( nv ) ); \ if (( m )->space == NULL) \ return REG_ESPACE; \ ( m )->vn = 0; \ } #define STATETEARDOWN(m) \ { \ free(( m )->space); \ } #define SETUP(v) (( v ) = &m->space[m->vn++ *m->g->nstates] ) #define onestate long #define INIT(o, n) (( o ) = ( n )) #define INC(o) (( o )++ ) #define ISSTATEIN(v, o) (( v )[o] ) /* some abbreviations; note that some of these know variable names! */ /* do "if I'm here, I can also be there" etc without branches */ #define FWD(dst, src, n) (( dst )[here + ( n )] |= ( src )[here] ) #define BACK(dst, src, n) (( dst )[here - ( n )] |= ( src )[here] ) #define ISSETBACK(v, n) (( v )[here - ( n )] ) /* function names */ #define LNAMES /* flag */ #include "engine.c" /* * - regexec - interface for matching * * We put this here so we can exploit knowledge of the state representation * when choosing which matcher to call. Also, by this point the matchers * have been prototyped. */ int /* 0 success, REG_NOMATCH failure */ regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) { struct re_guts *g = preg->re_g; #ifdef REDEBUG # define GOODFLAGS(f) ( f ) #else /* ifdef REDEBUG */ # define GOODFLAGS(f) (( f ) & ( REG_NOTBOL | REG_NOTEOL | REG_STARTEND )) #endif /* ifdef REDEBUG */ if (preg->re_magic != MAGIC1 || g->magic != MAGIC2) { return REG_BADPAT; } assert(!( g->iflags & BAD )); if (g->iflags & BAD) /* backstop for no-debug case */ { return REG_BADPAT; } eflags = GOODFLAGS(eflags); if (g->nstates <= CHAR_BIT * sizeof ( states1 ) && !( eflags & REG_LARGE )) { return smatcher(g, string, nmatch, pmatch, eflags); } else { return lmatcher(g, string, nmatch, pmatch, eflags); } } ================================================ FILE: regex/regfree.c ================================================ /* $OpenBSD: regfree.c,v 1.11 2015/12/28 22:27:03 mmcc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)regfree.c 8.3 (Berkeley) 3/20/94 */ #include #include #include #include #include #include "utils.h" #include "bsd_regex2.h" /* - regfree - free everything */ void regfree(regex_t *preg) { struct re_guts *g; if (preg->re_magic != MAGIC1) /* oops */ return; /* nice to complain, but hard */ g = preg->re_g; if (g == NULL || g->magic != MAGIC2) /* oops again */ return; preg->re_magic = 0; /* mark it invalid */ g->magic = 0; /* mark it invalid */ free(g->strip); free(g->sets); free(g->setbits); free(g->must); free(g); } ================================================ FILE: regex/utils.h ================================================ /* $OpenBSD: utils.h,v 1.4 2003/06/02 20:18:36 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 Henry Spencer. * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * All rights reserved. * * This code is derived from software contributed to Berkeley by * Henry Spencer. * * 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. * * @(#)utils.h 8.3 (Berkeley) 3/20/94 */ /* utility definitions */ #if defined(__managarm__) # if !defined(_POSIX2_RE_DUP_MAX) # define _POSIX2_RE_DUP_MAX 255 # endif #endif #define DUPMAX _POSIX2_RE_DUP_MAX #define INFINITY (DUPMAX + 1) #define NC (CHAR_MAX - CHAR_MIN + 1) typedef unsigned char uch; /* switch off assertions (if not already off) if no REDEBUG */ #ifndef REDEBUG # ifndef NDEBUG # define NDEBUG /* no assertions please */ # endif /* ifndef NDEBUG */ #endif /* ifndef REDEBUG */ #include ================================================ FILE: scripts/virecover ================================================ #!/usr/bin/env perl # $OpenBSD: recover,v 1.13 2018/09/17 15:41:17 millert Exp $ # SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2000, 2006, 2013, 2020 The NetBSD Foundation, Inc. # Copyright (c) 2021-2024 Jeffrey H. Johnson # Script to (safely) recover OpenBSD vi edit sessions. use warnings; use strict; use Fcntl; my $recoverdir = $ARGV[0] || "/var/tmp/vi.recover"; my $sendmail = "/usr/sbin/sendmail"; die "Sorry, $0 must be run as root\n" if $>; # Make the recovery dir if it does not already exist. if (!sysopen(DIR, $recoverdir, O_RDONLY|O_NOFOLLOW) || !stat(DIR)) { die "Warning! $recoverdir is a symbolic link! (ignoring)\n" if -l $recoverdir; mkdir($recoverdir, 01777) || die "Unable to create $recoverdir: $!\n"; chmod(01777, $recoverdir); exit(0); } # # Sanity check the vi recovery dir # die "Warning! $recoverdir is not a directory! (ignoring)\n" unless -d _; die "$0: can't chdir to $recoverdir: $!\n" unless chdir DIR; if (! -O _) { warn "Warning! $recoverdir is not owned by root! (fixing)\n"; chown(0, 0, "."); } if (((stat(_))[2] & 07777) != 01777) { warn "Warning! $recoverdir is not mode 01777! (fixing)\n"; chmod(01777, "."); } # Check editor backup files. opendir(RECDIR, ".") || die "$0: can't open $recoverdir: $!\n"; foreach my $file (readdir(RECDIR)) { next unless $file =~ /^vi\./; # # Unmodified vi editor backup files either have the # execute bit set or are zero length. Delete them. # Anything that is not a normal file gets deleted too. # lstat($file) || die "$0: can't stat $file: $!\n"; if (-x _ || ! -s _ || ! -f _) { unlink($file) unless -d _; } } # # It is possible to get incomplete recovery files if the editor crashes # at the right time. # rewinddir(RECDIR); foreach my $file (readdir(RECDIR)) { next unless $file =~ /^recover\./; if (!sysopen(RECFILE, $file, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) { warn "$0: can't open $file: $!\n"; next; } # # Delete anything that is not a regular file as that is either # filesystem corruption from fsck or an exploit attempt. # Real vi recovery files are created with mode 0600, ignore others. # if (!stat(RECFILE)) { warn "$0: can't stat $file: $!\n"; close(RECFILE); next; } if (((stat(_))[2] & 07777) != 0600) { close(RECFILE); next; } my $owner = (stat(_))[4]; if (! -f _ || ! -s _) { unlink($file) unless -d _; close(RECFILE); next; } # # Slurp in the recover.* file and search for X-vi-recover-path # (which should point to an existing vi.* file). # my @recfile = ; close(RECFILE); # # Delete any recovery files that have no (or more than one) # corresponding backup file. # my @backups = grep(m#^X-vi-recover-path:\s*\Q$recoverdir\E/+#, @recfile); if (@backups != 1) { unlink($file); next; } # # Make a copy of the backup file path. # We must not modify @backups directly since it contains # references to data in @recfile which we pipe to sendmail. # $backups[0] =~ m#^X-vi-recover-path:\s*\Q$recoverdir\E/+(.*)[\r\n]*$#; my $backup = $1; # # If backup file is not rooted in the recover dir, ignore it. # If backup file owner doesn't match recovery file owner, ignore it. # If backup file is zero length or not a regular file, remove it. # Else send mail to the user. # if ($backup =~ m#/# || !lstat($backup)) { unlink($file); } elsif ($owner != 0 && (stat(_))[4] != $owner) { unlink($file); } elsif (! -f _ || ! -s _) { unlink($file, $backup); } else { open(SENDMAIL, "|$sendmail -t") || die "$0: can't run $sendmail -t: $!\n"; print SENDMAIL @recfile; close(SENDMAIL); } } closedir(RECDIR); close(DIR); exit(0); ================================================ FILE: scripts/virecover.8 ================================================ .\" $NetBSD: virecover.8,v 1.1 2013/11/22 16:00:45 christos Exp $ .\" .\" SPDX-License-Identifier: BSD-2-Clause .\" .\" Copyright (c) 2006 The NetBSD Foundation, Inc. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" All rights reserved. .\" .\" This code is derived from software contributed to The NetBSD Foundation .\" by Jeremy C. Reed. .\" .\" 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. .\" .Dd October 9, 2006 .Dt VIRECOVER 8 .Os .Sh NAME .Nm vi.recover .Nd report recovered nvi edit sessions .Sh SYNOPSIS .Pa /usr/libexec/vi.recover .Sh DESCRIPTION The .Nm utility sends emails to users who have .Xr nvi 1 recovery files. .Pp This email gives the name of the file that was saved for recovery and instructions for recovering most, if not all, of the changes to the file. This is done by using the .Fl r option with .Xr nvi 1 . See the .Fl r option in .Xr nvi 1 for details. .Pp If the backup files have the execute bit set or are zero length, then they have not been modified, so .Nm deletes them to clean up. .Nm also removes recovery files that are corrupted, zero length, or do not have a corresponding backup file. .Pp .Nm is normally run automatically at boot time. .Sh FILES .Bl -tag -width "/var/tmp/vi.recover/recover.*" -compact .It Pa /var/tmp/vi.recover/recover.* .Xr nvi 1 recovery files .It Pa /var/tmp/vi.recover/vi.* .Xr nvi 1 editor backup files .El .Sh SEE ALSO .Xr nvi 1 .Sh AUTHORS This man page was originally written by .An Jeremy C. Reed Aq Mt reed@reedmedia.net . ================================================ FILE: vi/getc.c ================================================ /* $OpenBSD: getc.c,v 1.10 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * Character stream routines -- * These routines return the file a character at a time. There are two * special cases. First, the end of a line, end of a file, start of a * file and empty lines are returned as special cases, and no character * is returned. Second, empty lines include lines that have only white * space in them, because the vi search functions don't care about white * space, and this makes it easier for them to be consistent. */ /* * cs_init -- * Initialize character stream routines. * * PUBLIC: int cs_init(SCR *, VCS *); */ int cs_init(SCR *sp, VCS *csp) { int isempty; if (db_eget(sp, csp->cs_lno, (char **) &csp->cs_bp, &csp->cs_len, &isempty)) { if (isempty) msgq(sp, M_BERR, "Empty file"); return (1); } if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_ch = csp->cs_bp[csp->cs_cno]; } return (0); } /* * cs_next -- * Retrieve the next character. * * PUBLIC: int cs_next(SCR *, VCS *); */ int cs_next(SCR *sp, VCS *csp) { char *p; switch (csp->cs_flags) { case CS_EMP: /* EMP; get next line. */ case CS_EOL: /* EOL; get next line. */ if (db_get(sp, ++csp->cs_lno, 0, &p, &csp->cs_len)) { --csp->cs_lno; csp->cs_flags = CS_EOF; } else { csp->cs_bp = p; if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_ch = csp->cs_bp[csp->cs_cno = 0]; } } break; case 0: if (csp->cs_cno == csp->cs_len - 1) csp->cs_flags = CS_EOL; else csp->cs_ch = csp->cs_bp[++csp->cs_cno]; break; case CS_EOF: /* EOF. */ break; default: abort(); /* NOTREACHED */ } return (0); } /* * cs_fspace -- * If on a space, eat forward until something other than a * whitespace character. * * XXX * Semantics of checking the current character were coded for the fword() * function -- once the other word routines are converted, they may have * to change. * * PUBLIC: int cs_fspace(SCR *, VCS *); */ int cs_fspace(SCR *sp, VCS *csp) { if (csp->cs_flags != 0 || !isblank(csp->cs_ch)) return (0); for (;;) { if (cs_next(sp, csp)) return (1); if (csp->cs_flags != 0 || !isblank(csp->cs_ch)) break; } return (0); } /* * cs_fblank -- * Eat forward to the next non-whitespace character. * * PUBLIC: int cs_fblank(SCR *, VCS *); */ int cs_fblank(SCR *sp, VCS *csp) { for (;;) { if (cs_next(sp, csp)) return (1); if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || (csp->cs_flags == 0 && isblank(csp->cs_ch))) continue; break; } return (0); } /* * cs_prev -- * Retrieve the previous character. * * PUBLIC: int cs_prev(SCR *, VCS *); */ int cs_prev(SCR *sp, VCS *csp) { switch (csp->cs_flags) { case CS_EMP: /* EMP; get previous line. */ case CS_EOL: /* EOL; get previous line. */ if (csp->cs_lno == 1) { /* SOF. */ csp->cs_flags = CS_SOF; break; } if (db_get(sp, /* The line should exist. */ --csp->cs_lno, DBG_FATAL, (char **) &csp->cs_bp, &csp->cs_len)) { ++csp->cs_lno; return (1); } if (csp->cs_len == 0 || v_isempty(csp->cs_bp, csp->cs_len)) { csp->cs_cno = 0; csp->cs_flags = CS_EMP; } else { csp->cs_flags = 0; csp->cs_cno = csp->cs_len - 1; csp->cs_ch = csp->cs_bp[csp->cs_cno]; } break; case CS_EOF: /* EOF: get previous char. */ case 0: if (csp->cs_cno == 0) if (csp->cs_lno == 1) csp->cs_flags = CS_SOF; else csp->cs_flags = CS_EOL; else csp->cs_ch = csp->cs_bp[--csp->cs_cno]; break; case CS_SOF: /* SOF. */ break; default: abort(); /* NOTREACHED */ } return (0); } /* * cs_bblank -- * Eat backward to the next non-whitespace character. * * PUBLIC: int cs_bblank(SCR *, VCS *); */ int cs_bblank(SCR *sp, VCS *csp) { for (;;) { if (cs_prev(sp, csp)) return (1); if (csp->cs_flags == CS_EOL || csp->cs_flags == CS_EMP || (csp->cs_flags == 0 && isblank(csp->cs_ch))) continue; break; } return (0); } ================================================ FILE: vi/v_at.c ================================================ /* $OpenBSD: v_at.c,v 1.11 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_at -- @ * Execute a buffer. * * PUBLIC: int v_at(SCR *, VICMD *); */ int v_at(SCR *sp, VICMD *vp) { CB *cbp; CHAR_T name; TEXT *tp; size_t len; char nbuf[20]; /* * !!! * Historically, [@*] and [@*][@*] executed the most * recently executed buffer in ex mode. In vi mode, only @@ repeated * the last buffer. We change historic practice and make @* work from * vi mode as well, it's simpler and more consistent. * * My intent is that *[buffer] will, in the future, pass the buffer to * whatever interpreter is loaded. */ name = F_ISSET(vp, VC_BUFFER) ? vp->buffer : '@'; if (name == '@' || name == '*') { if (!F_ISSET(sp, SC_AT_SET)) { ex_emsg(sp, NULL, EXM_NOPREVBUF); return (1); } name = sp->at_lbuf; } F_SET(sp, SC_AT_SET); CBNAME(sp, cbp, name); if (cbp == NULL) { ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); return (1); } /* Save for reuse. */ sp->at_lbuf = name; /* * The buffer is executed in vi mode, while in vi mode, so simply * push it onto the terminal queue and continue. * * !!! * Historic practice is that if the buffer was cut in line mode, * were appended to each line as it was pushed onto * the stack. If the buffer was cut in character mode, * were appended to all lines but the last one. * * XXX * Historic practice is that execution of an @ buffer could be * undone by a single 'u' command, i.e. the changes were grouped * together. We don't get this right; I'm waiting for the new DB * logging code to be available. */ TAILQ_FOREACH_REVERSE(tp, &cbp->textq, _texth, q) if (((F_ISSET(cbp, CB_LMODE) || TAILQ_NEXT(tp, q)) && v_event_push(sp, NULL, "\n", 1, 0)) || v_event_push(sp, NULL, tp->lb, tp->len, 0)) return (1); /* * !!! * If any count was supplied, it applies to the first command in the * at buffer. */ if (F_ISSET(vp, VC_C1SET)) { len = snprintf(nbuf, sizeof(nbuf), "%lu", vp->count); if (len >= sizeof(nbuf)) len = sizeof(nbuf) - 1; if (v_event_push(sp, NULL, nbuf, len, 0)) return (1); } return (0); } ================================================ FILE: vi/v_ch.c ================================================ /* $OpenBSD: v_ch.c,v 1.10 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static void notfound(SCR *, CHAR_T); static void noprev(SCR *); /* * v_chrepeat -- [count]; * Repeat the last F, f, T or t search. * * PUBLIC: int v_chrepeat(SCR *, VICMD *); */ int v_chrepeat(SCR *sp, VICMD *vp) { vp->character = VIP(sp)->lastckey; switch (VIP(sp)->csearchdir) { case CNOTSET: noprev(sp); return (1); case FSEARCH: return (v_chF(sp, vp)); case fSEARCH: return (v_chf(sp, vp)); case TSEARCH: return (v_chT(sp, vp)); case tSEARCH: return (v_cht(sp, vp)); default: abort(); } /* NOTREACHED */ } /* * v_chrrepeat -- [count], * Repeat the last F, f, T or t search in the reverse direction. * * PUBLIC: int v_chrrepeat(SCR *, VICMD *); */ int v_chrrepeat(SCR *sp, VICMD *vp) { cdir_t savedir; int rval; vp->character = VIP(sp)->lastckey; savedir = VIP(sp)->csearchdir; switch (VIP(sp)->csearchdir) { case CNOTSET: noprev(sp); return (1); case FSEARCH: rval = v_chf(sp, vp); break; case fSEARCH: rval = v_chF(sp, vp); break; case TSEARCH: rval = v_cht(sp, vp); break; case tSEARCH: rval = v_chT(sp, vp); break; default: abort(); } VIP(sp)->csearchdir = savedir; return (rval); } /* * v_cht -- [count]tc * Search forward in the line for the character before the next * occurrence of the specified character. * * PUBLIC: int v_cht(SCR *, VICMD *); */ int v_cht(SCR *sp, VICMD *vp) { if (v_chf(sp, vp)) return (1); /* * v_chf places the cursor on the character, where the 't' * command wants it to its left. We know this is safe since * we had to move right for v_chf() to have succeeded. */ --vp->m_stop.cno; /* * Make any necessary correction to the motion decision made * by the v_chf routine. */ if (!ISMOTION(vp)) vp->m_final = vp->m_stop; VIP(sp)->csearchdir = tSEARCH; return (0); } /* * v_chf -- [count]fc * Search forward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chf(SCR *, VICMD *); */ int v_chf(SCR *sp, VICMD *vp) { size_t len; unsigned long cnt; int isempty, key; char *endp, *p, *startp; /* * !!! * If it's a dot command, it doesn't reset the key for which we're * searching, e.g. in "df1|f2|.|;", the ';' searches for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = fSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = (startp = p) + len; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (++p < endp && *p != (char) key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = p - startp; /* * Non-motion commands move to the end of the range. * Delete and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_chT -- [count]Tc * Search backward in the line for the character after the next * occurrence of the specified character. * * PUBLIC: int v_chT(SCR *, VICMD *); */ int v_chT(SCR *sp, VICMD *vp) { if (v_chF(sp, vp)) return (1); /* * v_chF places the cursor on the character, where the 'T' * command wants it to its right. We know this is safe since * we had to move left for v_chF() to have succeeded. */ ++vp->m_stop.cno; vp->m_final = vp->m_stop; VIP(sp)->csearchdir = TSEARCH; return (0); } /* * v_chF -- [count]Fc * Search backward in the line for the next occurrence of the * specified character. * * PUBLIC: int v_chF(SCR *, VICMD *); */ int v_chF(SCR *sp, VICMD *vp) { size_t len; unsigned long cnt; int isempty, key; char *endp, *p; /* * !!! * If it's a dot command, it doesn't reset the key for which * we're searching, e.g. in "df1|f2|.|;", the ';' searches * for a '2'. */ key = vp->character; if (!F_ISSET(vp, VC_ISDOT)) VIP(sp)->lastckey = key; VIP(sp)->csearchdir = FSEARCH; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto empty; return (1); } if (len == 0) { empty: notfound(sp, key); return (1); } endp = p - 1; p += vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt--;) { while (--p > endp && *p != (char) key); if (p == endp) { notfound(sp, key); return (1); } } vp->m_stop.cno = (p - endp) - 1; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); } static void noprev(SCR *sp) { msgq(sp, M_BERR, "No previous F, f, T or t search"); } static void notfound(SCR *sp, CHAR_T ch) { msgq(sp, M_BERR, "%s not found", KEY_NAME(sp, ch)); } ================================================ FILE: vi/v_cmd.c ================================================ /* $OpenBSD: v_cmd.c,v 1.5 2016/03/13 18:30:43 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * This array maps keystrokes to vi command functions. It is known * in ex/ex_usage.c that it takes four columns to name a vi character. */ VIKEYS const vikeys [MAXVIKEY + 1] = { /* 000 NUL -- The code in vi.c expects key 0 to be undefined. */ {NULL, 0, NULL, NULL}, /* 001 ^A */ {v_searchw, V_ABS|V_CNT|V_MOVE|V_KEYW|VM_CUTREQ|VM_RCM_SET, "[count]^A", "^A search forward for cursor word"}, /* 002 ^B */ {v_pageup, V_CNT|VM_RCM_SET, "[count]^B", "^B scroll up by screens"}, /* 003 ^C */ {NULL, 0, "^C", "^C interrupt an operation (e.g. read, write, search)"}, /* 004 ^D */ {v_hpagedown, V_CNT|VM_RCM_SET, "[count]^D", "^D scroll down by half screens (setting count)"}, /* 005 ^E */ {v_linedown, V_CNT, "[count]^E", "^E scroll down by lines"}, /* 006 ^F */ {v_pagedown, V_CNT|VM_RCM_SET, "[count]^F", "^F scroll down by screens"}, /* 007 ^G */ {v_status, 0, "^G", "^G file status"}, /* 010 ^H */ {v_left, V_CNT|V_MOVE|VM_RCM_SET, "[count]^H", "^H move left by characters"}, /* 011 ^I */ {NULL, 0, NULL, NULL}, /* 012 ^J */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^J", "^J move down by lines"}, /* 013 ^K */ {NULL, 0, NULL, NULL}, /* 014 ^L */ {v_redraw, 0, "^L", "^L redraw screen"}, /* 015 ^M */ {v_cr, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]^M", "^M move down by lines (to first non-blank)"}, /* 016 ^N */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^N", "^N move down by lines"}, /* 017 ^O */ {NULL, 0, NULL, NULL}, /* 020 ^P */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]^P", "^P move up by lines"}, /* 021 ^Q -- same as ^V if not used for hardware flow control. */ {NULL, 0, NULL, NULL}, /* 022 ^R */ {v_redraw, 0, "^R", "^R redraw screen"}, /* 023 ^S -- not available, used for hardware flow control. */ {NULL, 0, NULL, NULL}, /* 024 ^T */ {v_tagpop, V_ABS|VM_RCM_SET, "^T", "^T tag pop"}, /* 025 ^U */ {v_hpageup, V_CNT|VM_RCM_SET, "[count]^U", "^U half page up (set count)"}, /* 026 ^V */ {NULL, 0, "^V", "^V input a literal character"}, /* 027 ^W */ {v_screen, 0, "^W", "^W move to next screen"}, /* 030 ^X */ {NULL, 0, NULL, NULL}, /* 031 ^Y */ {v_lineup, V_CNT, "[count]^Y", "^Y page up by lines"}, /* 032 ^Z */ {v_suspend, V_SECURE, "^Z", "^Z suspend editor"}, /* 033 ^[ */ {NULL, 0, "^[ ", "^[ exit input mode, cancel partial commands"}, /* 034 ^\ */ {v_exmode, 0, "^\\", "^\\ switch to ex mode"}, /* 035 ^] */ {v_tagpush, V_ABS|V_KEYW|VM_RCM_SET, "^]", "^] tag push cursor word"}, /* 036 ^^ */ {v_switch, 0, "^^", "^^ switch to previous file"}, /* 037 ^_ */ {NULL, 0, NULL, NULL}, /* 040 ' ' */ {v_right, V_CNT|V_MOVE|VM_RCM_SET, "[count]' '", " move right by columns"}, /* 041 ! */ {v_filter, V_CNT|V_DOT|V_MOTION|V_SECURE|VM_RCM_SET, "[count]![count]motion command(s)", " ! filter through command(s) to motion"}, /* 042 " */ {NULL, 0, NULL, NULL}, /* 043 # */ {v_increment, V_CHAR|V_CNT|V_DOT|VM_RCM_SET, "[count]# +|-|#", " # number increment/decrement"}, /* 044 $ */ {v_dollar, V_CNT|V_MOVE|VM_RCM_SETLAST, " [count]$", " $ move to last column"}, /* 045 % */ {v_match, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "%", " % move to match"}, /* 046 & */ {v_again, 0, "&", " & repeat substitution"}, /* 047 ' */ {v_fmark, V_ABS_L|V_CHAR|V_MOVE|VM_LMODE|VM_RCM_SET, "'['a-z]", " ' move to mark (to first non-blank)"}, /* 050 ( */ {v_sentenceb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count](", " ( move back sentence"}, /* 051 ) */ {v_sentencef, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count])", " ) move forward sentence"}, /* 052 * */ {NULL, 0, NULL, NULL}, /* 053 + */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]+", " + move down by lines (to first non-blank)"}, /* 054 , */ {v_chrrepeat, V_CNT|V_MOVE|VM_RCM_SET, "[count],", " , reverse last F, f, T or t search"}, /* 055 - */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]-", " - move up by lines (to first non-blank)"}, /* 056 . */ {NULL, 0, ".", " . repeat the last command"}, /* 057 / */ {v_searchf, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "/RE[/ offset]", " / search forward"}, /* 060 0 */ {v_zero, V_MOVE|VM_RCM_SET, "0", " 0 move to first character"}, /* 061 1 */ {NULL, 0, NULL, NULL}, /* 062 2 */ {NULL, 0, NULL, NULL}, /* 063 3 */ {NULL, 0, NULL, NULL}, /* 064 4 */ {NULL, 0, NULL, NULL}, /* 065 5 */ {NULL, 0, NULL, NULL}, /* 066 6 */ {NULL, 0, NULL, NULL}, /* 067 7 */ {NULL, 0, NULL, NULL}, /* 070 8 */ {NULL, 0, NULL, NULL}, /* 071 9 */ {NULL, 0, NULL, NULL}, /* 072 : */ {v_ex, 0, ":command [| command] ...", " : ex command"}, /* 073 ; */ {v_chrepeat, V_CNT|V_MOVE|VM_RCM_SET, "[count];", " ; repeat last F, f, T or t search"}, /* 074 < */ {v_shiftl, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]<[count]motion", " < shift lines left to motion"}, /* 075 = */ {NULL, 0, NULL, NULL}, /* 076 > */ {v_shiftr, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]>[count]motion", " > shift lines right to motion"}, /* 077 ? */ {v_searchb, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "?RE[? offset]", " ? search backward"}, /* 100 @ */ {v_at, V_CNT|V_RBUF|VM_RCM_SET, "@buffer", " @ execute buffer"}, /* 101 A */ {v_iA, V_CNT|V_DOT|VM_RCM_SET, "[count]A", " A append to the line"}, /* 102 B */ {v_wordB, V_CNT|V_MOVE|VM_RCM_SET, "[count]B", " B move back bigword"}, /* 103 C */ {NULL, 0, "[buffer][count]C", " C change to end-of-line"}, /* 104 D */ {NULL, 0, "[buffer]D", " D delete to end-of-line"}, /* 105 E */ {v_wordE, V_CNT|V_MOVE|VM_RCM_SET, "[count]E", " E move to end of bigword"}, /* 106 F */ {v_chF, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]F character", " F character in line backward search"}, /* 107 G */ {v_lgoto, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETFNB, "[count]G", " G move to line"}, /* 110 H */ {v_home, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "[count]H", " H move to count lines from screen top"}, /* 111 I */ {v_iI, V_CNT|V_DOT|VM_RCM_SET, "[count]I", " I insert before first nonblank"}, /* 112 J */ {v_join, V_CNT|V_DOT|VM_RCM_SET, "[count]J", " J join lines"}, /* 113 K */ {NULL, 0, NULL, NULL}, /* 114 L */ {v_bottom, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "[count]L", " L move to screen bottom"}, /* 115 M */ {v_middle, V_ABS_L|V_CNT|V_MOVE|VM_LMODE|VM_RCM_SETNNB, "M", " M move to screen middle"}, /* 116 N */ {v_searchN, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "n", " N reverse last search"}, /* 117 O */ {v_iO, V_CNT|V_DOT|VM_RCM_SET, "[count]O", " O insert above line"}, /* 120 P */ {v_Put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer]P", " P insert before cursor from buffer"}, /* 121 Q */ {v_exmode, 0, "Q", " Q switch to ex mode"}, /* 122 R */ {v_Replace, V_CNT|V_DOT|VM_RCM_SET, "[count]R", " R replace characters"}, /* 123 S */ {NULL, 0, "[buffer][count]S", " S substitute for the line(s)"}, /* 124 T */ {v_chT, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]T character", " T before character in line backward search"}, /* 125 U */ {v_Undo, VM_RCM_SET, "U", " U restore the current line"}, /* 126 V */ {NULL, 0, NULL, NULL}, /* 127 W */ {v_wordW, V_CNT|V_MOVE|VM_RCM_SET, "[count]W", " W move to next bigword"}, /* 130 X */ {v_Xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]X", " X delete character before cursor"}, /* 131 Y */ {NULL, 0, "[buffer][count]Y", " Y copy line"}, /* 132 Z */ {v_zexit, 0, "ZZ", "ZZ save file and exit"}, /* 133 [ */ {v_sectionb, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, "[[", "[[ move back section"}, /* 134 \ */ {NULL, 0, NULL, NULL}, /* 135 ] */ {v_sectionf, V_ABS|V_CNT|V_MOVE|VM_RCM_SET, "]]", "]] move forward section"}, /* 136 ^ */ /* * DON'T set the VM_RCM_SETFNB flag, the function has to do the work * anyway, in case it's a motion component. DO set VM_RCM_SET, so * that any motion that's part of a command is preserved. */ {v_first, V_CNT|V_MOVE|VM_RCM_SET, "^", " ^ move to first non-blank"}, /* 137 _ */ /* * Needs both to set the VM_RCM_SETFNB flag, and to do the work * in the function, in case it's a delete. */ {v_cfirst, V_CNT|V_MOVE|VM_RCM_SETFNB, "_", " _ move to first non-blank"}, /* 140 ` */ {v_bmark, V_ABS_C|V_CHAR|V_MOVE|VM_CUTREQ|VM_RCM_SET, "`[`a-z]", " ` move to mark"}, /* 141 a */ {v_ia, V_CNT|V_DOT|VM_RCM_SET, "[count]a", " a append after cursor"}, /* 142 b */ {v_wordb, V_CNT|V_MOVE|VM_RCM_SET, "[count]b", " b move back word"}, /* 143 c */ {v_change, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]c[count]motion", " c change to motion"}, /* 144 d */ {v_delete, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]d[count]motion", " d delete to motion"}, /* 145 e */ {v_worde, V_CNT|V_MOVE|VM_RCM_SET, "[count]e", " e move to end of word"}, /* 146 f */ {v_chf, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]f character", " f character in line forward search"}, /* 147 g */ {NULL, 0, NULL, NULL}, /* 150 h */ {v_left, V_CNT|V_MOVE|VM_RCM_SET, "[count]h", " h move left by columns"}, /* 151 i */ {v_ii, V_CNT|V_DOT|VM_RCM_SET, "[count]i", " i insert before cursor"}, /* 152 j */ {v_down, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]j", " j move down by lines"}, /* 153 k */ {v_up, V_CNT|V_MOVE|VM_LMODE|VM_RCM, "[count]k", " k move up by lines"}, /* 154 l */ {v_right, V_CNT|V_MOVE|VM_RCM_SET, "[count]l", " l move right by columns"}, /* 155 m */ {v_mark, V_CHAR, "m[a-z]", " m set mark"}, /* 156 n */ {v_searchn, V_ABS_C|V_MOVE|VM_CUTREQ|VM_RCM_SET, "n", " n repeat last search"}, /* 157 o */ {v_io, V_CNT|V_DOT|VM_RCM_SET, "[count]o", " o append after line"}, /* 160 p */ {v_put, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer]p", " p insert after cursor from buffer"}, /* 161 q */ {NULL, 0, NULL, NULL}, /* 162 r */ {v_replace, V_CNT|V_DOT|VM_RCM_SET, "[count]r character", " r replace character"}, /* 163 s */ {v_subst, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]s", " s substitute character"}, /* 164 t */ {v_cht, V_CHAR|V_CNT|V_MOVE|VM_RCM_SET, "[count]t character", " t before character in line forward search"}, /* 165 u */ /* * DON'T set the V_DOT flag, it' more complicated than that. * See vi/vi.c for details. */ {v_undo, VM_RCM_SET, "u", " u undo last change"}, /* 166 v */ {NULL, 0, NULL, NULL}, /* 167 w */ {v_wordw, V_CNT|V_MOVE|VM_RCM_SET, "[count]w", " w move to next word"}, /* 170 x */ {v_xchar, V_CNT|V_DOT|V_OBUF|VM_RCM_SET, "[buffer][count]x", " x delete character"}, /* 171 y */ {v_yank, V_CNT|V_DOT|V_MOTION|V_OBUF|VM_RCM_SET, "[buffer][count]y[count]motion", " y copy text to motion into a cut buffer"}, /* 172 z */ /* * DON'T set the V_CHAR flag, the char isn't required, * so it's handled specially in getcmd(). */ {v_z, V_ABS_L|V_CNT|VM_RCM_SETFNB, "[line]z[window_size][-|.|+|^|]", " z reposition the screen"}, /* 173 { */ {v_paragraphb, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count]{", " { move back paragraph"}, /* 174 | */ {v_ncol, V_CNT|V_MOVE|VM_RCM_SET, "[count]|", " | move to column"}, /* 175 } */ {v_paragraphf, V_ABS|V_CNT|V_MOVE|VM_CUTREQ|VM_RCM_SET, "[count]}", " } move forward paragraph"}, /* 176 ~ */ {v_ulcase, V_CNT|V_DOT|VM_RCM_SET, "[count]~", " ~ reverse case"}, }; ================================================ FILE: vi/v_delete.c ================================================ /* $OpenBSD: v_delete.c,v 1.8 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_delete -- [buffer][count]d[count]motion * [buffer][count]D * Delete a range of text. * * PUBLIC: int v_delete(SCR *, VICMD *); */ int v_delete(SCR *sp, VICMD *vp) { recno_t nlines = 0; size_t len; int lmode; lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; /* Yank the lines. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode | (F_ISSET(vp, VM_CUTREQ) ? CUT_NUMREQ : CUT_NUMOPT))) return (1); /* Delete the lines. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* * Check for deletion of the entire file. Try to check a close * by line so we don't go to the end of the file unnecessarily. */ if (!db_exist(sp, vp->m_final.lno + 1)) { if (db_last(sp, &nlines)) return (1); if (nlines == 0) { vp->m_final.lno = 1; vp->m_final.cno = 0; return (0); } } /* * One special correction, in case we've deleted the current line or * character. We check it here instead of checking in every command * that can be a motion component. */ if (db_get(sp, vp->m_final.lno, 0, NULL, &len)) { if (db_get(sp, nlines, DBG_FATAL, NULL, &len)) return (1); vp->m_final.lno = nlines; } /* * !!! * Cursor movements, other than those caused by a line mode command * moving to another line, historically reset the relative position. * * This currently matches the check made in v_yank(), I'm hoping that * they should be consistent... */ if (!F_ISSET(vp, VM_LMODE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* Make sure the set cursor position exists. */ if (vp->m_final.cno >= len) vp->m_final.cno = len ? len - 1 : 0; } /* * !!! * The "dd" command moved to the first non-blank; "d" * didn't. */ if (F_ISSET(vp, VM_LDOUBLE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } return (0); } ================================================ FILE: vi/v_ex.c ================================================ /* $OpenBSD: v_ex.c,v 1.13 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int v_ecl(SCR *); static int v_ecl_init(SCR *); static int v_ecl_log(SCR *, TEXT *); static int v_ex_done(SCR *, VICMD *); static int v_exec_ex(SCR *, VICMD *, EXCMD *); /* * v_again -- & * Repeat the previous substitution. * * PUBLIC: int v_again(SCR *, VICMD *); */ int v_again(SCR *sp, VICMD *vp) { ARGS *ap[2], a; EXCMD cmd; ex_cinit(&cmd, C_SUBAGAIN, 2, vp->m_start.lno, vp->m_start.lno, 1, ap); ex_cadd(&cmd, &a, "", 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_exmode -- Q * Switch the editor into EX mode. * * PUBLIC: int v_exmode(SCR *, VICMD *); */ int v_exmode(SCR *sp, VICMD *vp) { GS *gp; gp = sp->gp; /* Try and switch screens -- the screen may not permit it. */ if (gp->scr_screen(sp, SC_EX)) { msgq(sp, M_ERR, "The Q command requires the ex terminal interface"); return (1); } (void)gp->scr_attr(sp, SA_ALTERNATE, 0); /* Save the current cursor position. */ sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); /* Switch to ex mode. */ F_CLR(sp, SC_VI | SC_SCR_VI); F_SET(sp, SC_EX); /* Move out of the vi screen. */ (void)ex_puts(sp, "\n"); return (0); } /* * v_join -- [count]J * Join lines together. * * PUBLIC: int v_join(SCR *, VICMD *); */ int v_join(SCR *sp, VICMD *vp) { EXCMD cmd; int lno; /* * YASC. * The general rule is that '#J' joins # lines, counting the current * line. However, 'J' and '1J' are the same as '2J', i.e. join the * current and next lines. This doesn't map well into the ex command * (which takes two line numbers), so we handle it here. Note that * we never test for EOF -- historically going past the end of file * worked just fine. */ lno = vp->m_start.lno + 1; if (F_ISSET(vp, VC_C1SET) && vp->count > 2) lno = vp->m_start.lno + (vp->count - 1); ex_cinit(&cmd, C_JOIN, 2, vp->m_start.lno, lno, 0, NULL); return (v_exec_ex(sp, vp, &cmd)); } /* * v_shiftl -- [count]m_start.lno, vp->m_stop.lno, 0, ap); ex_cadd(&cmd, &a, "<", 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_shiftr -- [count]>motion * Shift lines right. * * PUBLIC: int v_shiftr(SCR *, VICMD *); */ int v_shiftr(SCR *sp, VICMD *vp) { ARGS *ap[2], a; EXCMD cmd; ex_cinit(&cmd, C_SHIFTR, 2, vp->m_start.lno, vp->m_stop.lno, 0, ap); ex_cadd(&cmd, &a, ">", 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_suspend -- ^Z * Suspend vi. * * PUBLIC: int v_suspend(SCR *, VICMD *); */ int v_suspend(SCR *sp, VICMD *vp) { ARGS *ap[2], a; EXCMD cmd; ex_cinit(&cmd, C_STOP, 0, OOBLNO, OOBLNO, 0, ap); ex_cadd(&cmd, &a, "suspend", sizeof("suspend") - 1); return (v_exec_ex(sp, vp, &cmd)); } /* * v_switch -- ^^ * Switch to the previous file. * * PUBLIC: int v_switch(SCR *, VICMD *); */ int v_switch(SCR *sp, VICMD *vp) { ARGS *ap[2], a; EXCMD cmd; char *name; /* * Try the alternate file name, then the previous file * name. Use the real name, not the user's current name. */ if (sp->alt_name == NULL) { msgq(sp, M_ERR, "No previous file to edit"); return (1); } if ((name = strdup(sp->alt_name)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* If autowrite is set, write out the file. */ if (file_m1(sp, 0, FS_ALL)) { free(name); return (1); } ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, ap); ex_cadd(&cmd, &a, name, strlen(name)); return (v_exec_ex(sp, vp, &cmd)); } /* * v_tagpush -- ^[ * Do a tag search on the cursor keyword. * * PUBLIC: int v_tagpush(SCR *, VICMD *); */ int v_tagpush(SCR *sp, VICMD *vp) { ARGS *ap[2], a; EXCMD cmd; ex_cinit(&cmd, C_TAG, 0, OOBLNO, 0, 0, ap); ex_cadd(&cmd, &a, VIP(sp)->keyw, strlen(VIP(sp)->keyw)); return (v_exec_ex(sp, vp, &cmd)); } /* * v_tagpop -- ^T * Pop the tags stack. * * PUBLIC: int v_tagpop(SCR *, VICMD *); */ int v_tagpop(SCR *sp, VICMD *vp) { EXCMD cmd; ex_cinit(&cmd, C_TAGPOP, 0, OOBLNO, 0, 0, NULL); return (v_exec_ex(sp, vp, &cmd)); } /* * v_filter -- [count]!motion command(s) * Run range through shell commands, replacing text. * * PUBLIC: int v_filter(SCR *, VICMD *); */ int v_filter(SCR *sp, VICMD *vp) { EXCMD cmd; TEXT *tp; /* * !!! * Historical vi permitted "!!" in an empty file, and it's handled * as a special case in the ex_bang routine. Don't modify this setup * without understanding that one. In particular, note that we're * manipulating the ex argument structures behind ex's back. * * !!! * Historical vi did not permit the '!' command to be associated with * a non-line oriented motion command, in general, although it did * with search commands. So, !f; and !w would fail, but !/; * would succeed, even if they all moved to the same location in the * current line. I don't see any reason to disallow '!' using any of * the possible motion commands. * * !!! * Historical vi ran the last bang command if N or n was used as the * search motion. */ if (F_ISSET(vp, VC_ISDOT) || ISCMD(vp->rkp, 'N') || ISCMD(vp->rkp, 'n')) { ex_cinit(&cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL); EXP(sp)->argsoff = 0; /* XXX */ if (argv_exp1(sp, &cmd, "!", 1, 1)) return (1); cmd.argc = EXP(sp)->argsoff; /* XXX */ cmd.argv = EXP(sp)->args; /* XXX */ return (v_exec_ex(sp, vp, &cmd)); } /* Get the command from the user. */ if (v_tcmd(sp, vp, '!', TXT_BS | TXT_CR | TXT_ESCAPE | TXT_FILEC | TXT_PROMPT)) return (1); /* * Check to see if the user changed their mind. * * !!! * Entering on an empty line was historically an error, * this implementation doesn't bother. */ tp = TAILQ_FIRST(&sp->tiq); if (tp->term != TERM_OK) { vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; return (0); } /* Home the cursor. */ vs_home(sp); ex_cinit(&cmd, C_BANG, 2, vp->m_start.lno, vp->m_stop.lno, 0, NULL); EXP(sp)->argsoff = 0; /* XXX */ if (argv_exp1(sp, &cmd, tp->lb + 1, tp->len - 1, 1)) return (1); cmd.argc = EXP(sp)->argsoff; /* XXX */ cmd.argv = EXP(sp)->args; /* XXX */ return (v_exec_ex(sp, vp, &cmd)); } /* * v_event_exec -- * Execute some command(s) based on an event. * * PUBLIC: int v_event_exec(SCR *, VICMD *); */ int v_event_exec(SCR *sp, VICMD *vp) { EXCMD cmd; switch (vp->ev.e_event) { case E_QUIT: ex_cinit(&cmd, C_QUIT, 0, OOBLNO, OOBLNO, 0, NULL); break; case E_WRITE: ex_cinit(&cmd, C_WRITE, 0, OOBLNO, OOBLNO, 0, NULL); break; default: abort(); } return (v_exec_ex(sp, vp, &cmd)); } /* * v_exec_ex -- * Execute an ex command. */ static int v_exec_ex(SCR *sp, VICMD *vp, EXCMD *exp) { int rval; rval = exp->cmd->fn(sp, exp); return (v_ex_done(sp, vp) || rval); } /* * v_ex -- : * Execute a colon command line. * * PUBLIC: int v_ex(SCR *, VICMD *); */ int v_ex(SCR *sp, VICMD *vp) { GS *gp; TEXT *tp; int do_cedit, do_resolution, ifcontinue; gp = sp->gp; /* * !!! * If we put out more than a single line of messages, or ex trashes * the screen, the user may continue entering ex commands. We find * this out when we do the screen/message resolution. We can't enter * completely into ex mode however, because the user can elect to * return into vi mode by entering any key, i.e. we have to be in raw * mode. */ for (do_cedit = do_resolution = 0;;) { /* * !!! * There may already be an ex command waiting to run. If * so, we continue with it. */ if (!EXCMD_RUNNING(gp)) { /* Get a command. */ if (v_tcmd(sp, vp, ':', TXT_BS | TXT_CEDIT | TXT_FILEC | TXT_PROMPT)) return (1); tp = TAILQ_FIRST(&sp->tiq); /* * If the user entered a single , they want to * edit their colon command history. If they already * entered some text, move it into the edit history. */ if (tp->term == TERM_CEDIT) { if (tp->len > 1 && v_ecl_log(sp, tp)) return (1); do_cedit = 1; break; } /* If the user changed their mind, return. */ if (tp->term != TERM_OK) break; /* Log the command. */ if (O_STR(sp, O_CEDIT) != NULL && v_ecl_log(sp, tp)) return (1); /* Push a command on the command stack. */ if (ex_run_str(sp, NULL, tp->lb, tp->len, 0, 1)) return (1); } /* Home the cursor. */ vs_home(sp); /* * !!! * If the editor wrote the screen behind curses back, put out * a so that we don't overwrite the user's command * with its output or the next want-to-continue? message. This * doesn't belong here, but I can't find another place to put * it. See, we resolved the output from the last ex command, * and the user entered another one. This is the only place * where we have control before the ex command writes output. * We could get control in vs_msg(), but we have no way to know * if command didn't put out any output when we try and resolve * this command. This fixes a bug where combinations of ex * commands, e.g. ":set:!date:set" didn't look right. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) (void)putchar('\n'); /* Call the ex parser. */ (void)ex_cmd(sp); /* Flush ex messages. */ (void)ex_fflush(sp); /* Resolve any messages. */ if (vs_ex_resolve(sp, &ifcontinue)) return (1); /* * Continue or return. If continuing, make sure that we * eventually do resolution. */ if (!ifcontinue) break; do_resolution = 1; /* If we're continuing, it's a new command. */ ++sp->ccnt; } /* * If the user previously continued an ex command, we have to do * resolution to clean up the screen. Don't wait, we already did * that. */ if (do_resolution) { F_SET(sp, SC_EX_WAIT_NO); if (vs_ex_resolve(sp, &ifcontinue)) return (1); } /* Cleanup from the ex command. */ if (v_ex_done(sp, vp)) return (1); /* The user may want to edit their colon command history. */ if (do_cedit) return (v_ecl(sp)); return (0); } /* * v_ex_done -- * Cleanup from an ex command. */ static int v_ex_done(SCR *sp, VICMD *vp) { size_t len; /* * The only cursor modifications are real, however, the underlying * line may have changed; don't trust anything. This code has been * a remarkably fertile place for bugs. Do a reality check on a * cursor value, and make sure it's okay. If necessary, change it. * Ex keeps track of the line number, but it cares less about the * column and it may have disappeared. * * Don't trust ANYTHING. * * XXX * Ex will soon have to start handling the column correctly; see * the POSIX 1003.2 standard. */ if (db_eget(sp, sp->lno, NULL, &len, NULL)) { sp->lno = 1; sp->cno = 0; } else if (sp->cno >= len) sp->cno = len ? len - 1 : 0; vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; /* * Don't re-adjust the cursor after executing an ex command, * and ex movements are permanent. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); return (0); } /* * v_ecl -- * Start an edit window on the colon command-line commands. */ static int v_ecl(SCR *sp) { GS *gp; SCR *new; /* Initialize the screen, if necessary. */ gp = sp->gp; if (gp->ccl_sp == NULL && v_ecl_init(sp)) return (1); /* Get a new screen. */ if (screen_init(gp, sp, &new)) return (1); if (vs_split(sp, new, 1)) { (void)screen_end(new); return (1); } /* Attach to the screen. */ new->ep = gp->ccl_sp->ep; ++new->ep->refcnt; new->frp = gp->ccl_sp->frp; new->frp->flags = sp->frp->flags; /* Move the cursor to the end. */ (void)db_last(new, &new->lno); if (new->lno == 0) new->lno = 1; /* Remember the originating window. */ sp->ccl_parent = sp; /* It's a special window. */ F_SET(new, SC_COMEDIT); /* Set up the switch. */ sp->nextdisp = new; F_SET(sp, SC_SSWITCH); return (0); } /* * v_ecl_exec -- * Execute a command from a colon command-line window. * * PUBLIC: int v_ecl_exec(SCR *); */ int v_ecl_exec(SCR *sp) { size_t len; char *p; if (db_get(sp, sp->lno, 0, &p, &len) && sp->lno == 1) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } if (len == 0) { msgq(sp, M_BERR, "No ex command to execute"); return (1); } /* Push the command on the command stack. */ if (ex_run_str(sp, NULL, p, len, 0, 0)) return (1); /* Set up the switch. */ sp->nextdisp = sp->ccl_parent; F_SET(sp, SC_EXIT); return (0); } /* * v_ecl_log -- * Log a command into the colon command-line log file. */ static int v_ecl_log(SCR *sp, TEXT *tp) { EXF *save_ep; recno_t lno; int rval; /* Initialize the screen, if necessary. */ if (sp->gp->ccl_sp == NULL && v_ecl_init(sp)) return (1); /* * Don't log colon command window commands into the colon command * window... */ if (sp->ep == sp->gp->ccl_sp->ep) return (0); /* * XXX * Swap the current EXF with the colon command file EXF. This * isn't pretty, but too many routines "know" that sp->ep points * to the current EXF. */ save_ep = sp->ep; sp->ep = sp->gp->ccl_sp->ep; if (db_last(sp, &lno)) { sp->ep = save_ep; return (1); } rval = db_append(sp, 0, lno, tp->lb, tp->len); sp->ep = save_ep; return (rval); } /* * v_ecl_init -- * Initialize the colon command-line log file. */ static int v_ecl_init(SCR *sp) { FREF *frp; GS *gp; gp = sp->gp; /* Get a temporary file. */ if ((frp = file_add(sp, NULL)) == NULL) return (1); /* * XXX * Create a screen -- the file initialization code wants one. */ if (screen_init(gp, sp, &gp->ccl_sp)) return (1); if (file_init(gp->ccl_sp, frp, NULL, 0)) { (void)screen_end(gp->ccl_sp); gp->ccl_sp = 0; return (1); } /* The underlying file isn't recoverable. */ F_CLR(gp->ccl_sp->ep, F_RCV_ON); return (0); } ================================================ FILE: vi/v_increment.c ================================================ /* $OpenBSD: v_increment.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static char * const fmt[] = { #define DEC 0 "%ld", #define SDEC 1 "%+ld", #define HEXC 2 "0X%0*lX", #define HEXL 3 "0x%0*lx", #define OCTAL 4 "%#0*lo", }; static void inc_err(SCR *, enum nresult); /* * v_increment -- [count]#[#+-] * Increment/decrement a keyword number. * * PUBLIC: int v_increment(SCR *, VICMD *); */ int v_increment(SCR *sp, VICMD *vp) { enum nresult nret; unsigned long ulval; long change, ltmp, lval; size_t beg, blen, end, len, nlen, wlen; int base, isempty, rval; char *bp, *ntype, *p, *t, nbuf[100]; /* Validate the operator. */ if (vp->character == '#') vp->character = '+'; if (vp->character != '+' && vp->character != '-') { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } /* If new value set, save it off, but it has to fit in a long. */ if (F_ISSET(vp, VC_C1SET)) { if (vp->count > LONG_MAX) { inc_err(sp, NUM_OVER); return (1); } change = vp->count; } else change = 1; /* Get the line. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nonum; return (1); } /* * Skip any leading space before the number. Getting a cursor word * implies moving the cursor to its beginning, if we moved, refresh * now. */ for (beg = vp->m_start.cno; beg < len && isspace(p[beg]); ++beg); if (beg >= len) goto nonum; if (beg != vp->m_start.cno) { sp->cno = beg; (void)vs_refresh(sp, 0); } #undef ishex #define ishex(c) (isdigit(c) || strchr("abcdefABCDEF", (c))) #undef isoctal #define isoctal(c) (isdigit(c) && (c) != '8' && (c) != '9') /* * Look for 0[Xx], or leading + or - signs, guess at the base. * The character after that must be a number. Wlen is set to * the remaining characters in the line that could be part of * the number. */ wlen = len - beg; if (p[beg] == '0' && wlen > 2 && (p[beg + 1] == 'X' || p[beg + 1] == 'x')) { base = 16; end = beg + 2; if (!ishex(p[end])) goto decimal; ntype = p[beg + 1] == 'X' ? fmt[HEXC] : fmt[HEXL]; } else if (p[beg] == '0' && wlen > 1) { base = 8; end = beg + 1; if (!isoctal(p[end])) goto decimal; ntype = fmt[OCTAL]; } else if (wlen >= 1 && (p[beg] == '+' || p[beg] == '-')) { base = 10; end = beg + 1; ntype = fmt[SDEC]; if (!isdigit(p[end])) goto nonum; } else { decimal: base = 10; end = beg; ntype = fmt[DEC]; if (!isdigit(p[end])) { nonum: msgq(sp, M_ERR, "Cursor not in a number"); return (1); } } /* Find the end of the word, possibly correcting the base. */ while (++end < len) { switch (base) { case 8: if (isoctal(p[end])) continue; if (p[end] == '8' || p[end] == '9') { base = 10; ntype = fmt[DEC]; continue; } break; case 10: if (isdigit(p[end])) continue; break; case 16: if (ishex(p[end])) continue; break; default: abort(); /* NOTREACHED */ } break; } wlen = (end - beg); /* * XXX * If the line was at the end of the buffer, we have to copy it * so we can guarantee that it's NULL-terminated. We make the * buffer big enough to fit the line changes as well, and only * allocate once. */ GET_SPACE_RET(sp, bp, blen, len + 50); if (bp == NULL) { nret = NUM_UNDER; goto err; } if (end == len) { memmove(bp, &p[beg], wlen); bp[wlen] = '\0'; t = bp; } else t = &p[beg]; /* * Octal or hex deal in unsigned longs, everything else is done * in signed longs. */ if (base == 10) { if ((nret = nget_slong(&lval, t, NULL, 10)) != NUM_OK) goto err; ltmp = vp->character == '-' ? -change : change; if (lval > 0 && ltmp > 0 && !NPFITS(LONG_MAX, lval, ltmp)) { nret = NUM_OVER; goto err; } if (lval < 0 && ltmp < 0 && !NNFITS(LONG_MIN, lval, ltmp)) { nret = NUM_UNDER; goto err; } lval += ltmp; /* If we cross 0, signed numbers lose their sign. */ if (lval == 0 && ntype == fmt[SDEC]) ntype = fmt[DEC]; nlen = snprintf(nbuf, sizeof(nbuf), ntype, lval); if (nlen >= sizeof(nbuf)) nlen = sizeof(nbuf) - 1; } else { if ((nret = nget_uslong(&ulval, t, NULL, base)) != NUM_OK) goto err; if (vp->character == '+') { if (!NPFITS(ULONG_MAX, ulval, change)) { nret = NUM_OVER; goto err; } ulval += change; } else { if (ulval < change) { nret = NUM_UNDER; goto err; } ulval -= change; } /* Correct for literal "0[Xx]" in format. */ if (base == 16) wlen -= 2; nlen = snprintf(nbuf, sizeof(nbuf), ntype, wlen, ulval); if (nlen >= sizeof(nbuf)) nlen = sizeof(nbuf) - 1; } /* Build the new line. */ if (bp == NULL) { nret = NUM_UNDER; goto err; } memmove(bp, p, beg); memmove(bp + beg, nbuf, nlen); memmove(bp + beg + nlen, p + end, len - beg - (end - beg)); len = beg + nlen + (len - beg - (end - beg)); nret = NUM_OK; (void)nret; rval = db_set(sp, vp->m_start.lno, bp, len); if (0) { err: rval = 1; inc_err(sp, nret); } if (bp != NULL) FREE_SPACE(sp, bp, blen); return (rval); } static void inc_err(SCR *sp, enum nresult nret) { switch (nret) { case NUM_ERR: break; case NUM_OK: abort(); /* NOREACHED */ case NUM_OVER: msgq(sp, M_ERR, "Resulting number too large"); break; case NUM_UNDER: msgq(sp, M_ERR, "Resulting number too small"); break; } } ================================================ FILE: vi/v_init.c ================================================ /* $OpenBSD: v_init.c,v 1.8 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_screen_copy -- * Copy vi screen. * * PUBLIC: int v_screen_copy(SCR *, SCR *); */ int v_screen_copy(SCR *orig, SCR *sp) { VI_PRIVATE *ovip, *nvip; /* Create the private vi structure. */ CALLOC_RET(orig, nvip, 1, sizeof(VI_PRIVATE)); sp->vi_private = nvip; /* Invalidate the line size cache. */ VI_SCR_CFLUSH(nvip); if (orig == NULL) { nvip->csearchdir = CNOTSET; } else { ovip = VIP(orig); /* User can replay the last input, but nothing else. */ if (ovip->rep_len != 0) { MALLOC_RET(orig, nvip->rep, ovip->rep_len); memmove(nvip->rep, ovip->rep, ovip->rep_len); nvip->rep_len = ovip->rep_len; } /* Copy the paragraph/section information. */ if (ovip->ps != NULL && (nvip->ps = v_strdup(sp, ovip->ps, strlen(ovip->ps))) == NULL) return (1); nvip->lastckey = ovip->lastckey; nvip->csearchdir = ovip->csearchdir; nvip->srows = ovip->srows; } return (0); } /* * v_screen_end -- * End a vi screen. * * PUBLIC: int v_screen_end(SCR *); */ int v_screen_end(SCR *sp) { VI_PRIVATE *vip; if ((vip = VIP(sp)) == NULL) return (0); free(vip->keyw); free(vip->rep); free(vip->ps); free(HMAP); free(vip); sp->vi_private = NULL; return (0); } /* * v_optchange -- * Handle change of options for vi. * * PUBLIC: int v_optchange(SCR *, int, char *, unsigned long *); */ int v_optchange(SCR *sp, int offset, char *str, unsigned long *valp) { switch (offset) { case O_PARAGRAPHS: return (v_buildps(sp, str, O_STR(sp, O_SECTIONS))); case O_SECTIONS: return (v_buildps(sp, O_STR(sp, O_PARAGRAPHS), str)); case O_WINDOW: return (vs_crel(sp, *valp)); } return (0); } ================================================ FILE: vi/v_itxt.c ================================================ /* $OpenBSD: v_itxt.c,v 1.8 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * Repeated input in the historic vi is mostly wrong and this isn't very * backward compatible. For example, if the user entered "3Aab\ncd" in * the historic vi, the "ab" was repeated 3 times, and the "\ncd" was then * appended to the result. There was also a hack which I don't remember * right now, where "3o" would open 3 lines and then let the user fill them * in, to make screen movements on 300 baud modems more tolerable. I don't * think it's going to be missed. * * !!! * There's a problem with the way that we do logging for change commands with * implied motions (e.g. A, I, O, cc, etc.). Since the main vi loop logs the * starting cursor position before the change command "moves" the cursor, the * cursor position to which we return on undo will be where the user entered * the change command, not the start of the change. Several of the following * routines re-log the cursor to make this work correctly. Historic vi tried * to do the same thing, and mostly got it right. (The only spectacular way * it fails is if the user entered 'o' from anywhere but the last character of * the line, the undo returned the cursor to the start of the line. If the * user was on the last character of the line, the cursor returned to that * position.) We also check for mapped keys waiting, i.e. if we're in the * middle of a map, don't bother logging the cursor. */ #define LOG_CORRECT { \ if (!MAPPED_KEYS_WAITING(sp)) \ (void)log_cursor(sp); \ } static u_int32_t set_txt_std(SCR *, VICMD *, u_int32_t); /* * v_iA -- [count]A * Append text to the end of the line. * * PUBLIC: int v_iA(SCR *, VICMD *); */ int v_iA(SCR *sp, VICMD *vp) { size_t len; if (!db_get(sp, vp->m_start.lno, 0, NULL, &len)) sp->cno = len == 0 ? 0 : len - 1; LOG_CORRECT; return (v_ia(sp, vp)); } /* * v_ia -- [count]a * [count]A * Append text to the cursor position. * * PUBLIC: int v_ia(SCR *, VICMD *); */ int v_ia(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; char *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_APPEND; sp->lno = vp->m_start.lno; /* Move the cursor one column to the right and repaint the screen. */ if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else if (len) { if (len == sp->cno + 1) { sp->cno = len; LF_SET(TXT_APPENDEOL); } else ++sp->cno; } else LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_iI -- [count]I * Insert text at the first nonblank. * * PUBLIC: int v_iI(SCR *, VICMD *); */ int v_iI(SCR *sp, VICMD *vp) { sp->cno = 0; if (nonblank(sp, vp->m_start.lno, &sp->cno)) return (1); LOG_CORRECT; return (v_ii(sp, vp)); } /* * v_ii -- [count]i * [count]I * Insert text at the cursor position. * * PUBLIC: int v_ii(SCR *, VICMD *); */ int v_ii(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; char *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_INSERT; sp->lno = vp->m_start.lno; if (db_eget(sp, sp->lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) LF_SET(TXT_APPENDEOL); return (v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } enum which { o_cmd, O_cmd }; static int io(SCR *, VICMD *, enum which); /* * v_iO -- [count]O * Insert text above this line. * * PUBLIC: int v_iO(SCR *, VICMD *); */ int v_iO(SCR *sp, VICMD *vp) { return (io(sp, vp, O_cmd)); } /* * v_io -- [count]o * Insert text after this line. * * PUBLIC: int v_io(SCR *, VICMD *); */ int v_io(SCR *sp, VICMD *vp) { return (io(sp, vp, o_cmd)); } static int io(SCR *sp, VICMD *vp, enum which cmd) { recno_t ai_line, lno; size_t len; u_int32_t flags; char *p; flags = set_txt_std(sp, vp, TXT_ADDNEWLINE | TXT_APPENDEOL); sp->showmode = SM_INSERT; if (sp->lno == 1) { if (db_last(sp, &lno)) return (1); if (lno != 0) goto insert; p = NULL; len = 0; ai_line = OOBLNO; } else { insert: p = ""; sp->cno = 0; LOG_CORRECT; if (cmd == O_cmd) { if (db_insert(sp, sp->lno, p, 0)) return (1); if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) return (1); ai_line = sp->lno + 1; } else { if (db_append(sp, 1, sp->lno, p, 0)) return (1); if (db_get(sp, ++sp->lno, DBG_FATAL, &p, &len)) return (1); ai_line = sp->lno - 1; } } return (v_txt(sp, vp, NULL, p, len, 0, ai_line, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_change -- [buffer][count]c[count]motion * [buffer][count]C * [buffer][count]S * Change command. * * PUBLIC: int v_change(SCR *, VICMD *); */ int v_change(SCR *sp, VICMD *vp) { size_t blen, len; u_int32_t flags; int isempty, lmode, rval; char *bp, *p; /* * 'c' can be combined with motion commands that set the resulting * cursor position, i.e. "cG". Clear the VM_RCM flags and make the * resulting cursor position stick, inserting text has its own rules * for cursor positioning. */ F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* * Find out if the file is empty, it's easier to handle it as a * special case. */ if (vp->m_start.lno == vp->m_stop.lno && db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); return (v_ia(sp, vp)); } flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; /* * Move the cursor to the start of the change. Note, if autoindent * is turned on, the cc command in line mode changes from the first * *non-blank* character of the line, not the first character. And, * to make it just a bit more exciting, the initial space is handled * as auto-indent characters. */ lmode = F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0; if (lmode) { vp->m_start.cno = 0; if (O_ISSET(sp, O_AUTOINDENT)) { if (nonblank(sp, vp->m_start.lno, &vp->m_start.cno)) return (1); LF_SET(TXT_AICHARS); } } sp->lno = vp->m_start.lno; sp->cno = vp->m_start.cno; LOG_CORRECT; /* * If not in line mode and changing within a single line, copy the * text and overwrite it. */ if (!lmode && vp->m_start.lno == vp->m_stop.lno) { /* * !!! * Historic practice, c did not cut into the numeric buffers, * only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * It's trickier if in line mode or changing over multiple lines. If * we're in line mode delete all of the lines and insert a replacement * line which the user edits. If there was leading whitespace in the * first line being changed, we copy it and use it as the replacement. * If we're not in line mode, we delete the text and start inserting. * * !!! * Copy the text. Historic practice, c did not cut into the numeric * buffers, only the unnamed one. */ if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines and there's leading text. */ if (lmode && vp->m_start.cno) { /* * Get a copy of the first line changed, and copy out the * leading text. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); GET_SPACE_RET(sp, bp, blen, vp->m_start.cno); memmove(bp, p, vp->m_start.cno); } else bp = NULL; /* Delete the text. */ if (del(sp, &vp->m_start, &vp->m_stop, lmode)) return (1); /* If replacing entire lines, insert a replacement line. */ if (lmode) { if (db_insert(sp, vp->m_start.lno, bp, vp->m_start.cno)) return (1); sp->lno = vp->m_start.lno; len = sp->cno = vp->m_start.cno; } /* Get the line we're editing. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; } /* Check to see if we're appending to the line. */ if (vp->m_start.cno >= len) LF_SET(TXT_APPENDEOL); rval = v_txt(sp, vp, NULL, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (rval); } /* * v_Replace -- [count]R * Overwrite multiple characters. * * PUBLIC: int v_Replace(SCR *, VICMD *); */ int v_Replace(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; char *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_REPLACE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_OVERWRITE | TXT_REPLACE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = len ? len - 1 : 0; return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, F_ISSET(vp, VC_C1SET) ? vp->count : 1, flags)); } /* * v_subst -- [buffer][count]s * Substitute characters. * * PUBLIC: int v_subst(SCR *, VICMD *); */ int v_subst(SCR *sp, VICMD *vp) { size_t len; u_int32_t flags; int isempty; char *p; flags = set_txt_std(sp, vp, 0); sp->showmode = SM_CHANGE; if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); len = 0; LF_SET(TXT_APPENDEOL); } else { if (len == 0) LF_SET(TXT_APPENDEOL); LF_SET(TXT_EMARK | TXT_OVERWRITE); } vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0); if (vp->m_stop.cno > len - 1) vp->m_stop.cno = len - 1; if (p != NULL && cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (v_txt(sp, vp, &vp->m_stop, p, len, 0, OOBLNO, 1, flags)); } /* * set_txt_std -- * Initialize text processing flags. */ static u_int32_t set_txt_std(SCR *sp, VICMD *vp, u_int32_t flags) { LF_SET(TXT_CNTRLT | TXT_ESCAPE | TXT_MAPINPUT | TXT_RECORD | TXT_RESOLVE); if (F_ISSET(vp, VC_ISDOT)) LF_SET(TXT_REPLAY); if (O_ISSET(sp, O_ALTWERASE)) LF_SET(TXT_ALTWERASE); if (O_ISSET(sp, O_AUTOINDENT)) LF_SET(TXT_AUTOINDENT); if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_SHOWMATCH)) LF_SET(TXT_SHOWMATCH); if (F_ISSET(sp, SC_SCRIPT)) LF_SET(TXT_CR); if (O_ISSET(sp, O_TTYWERASE)) LF_SET(TXT_TTYWERASE); /* * !!! * Mapped keys were sometimes unaffected by the wrapmargin option * in the historic 4BSD vi. Consider the following commands, where * each is executed on an empty line, in an 80 column screen, with * the wrapmargin value set to 60. * * aABC DEF .... * :map K aABC DEF ^VKKKKK * :map K 5aABC DEF ^VK * * The first and second commands are affected by wrapmargin. The * third is not. (If the inserted text is itself longer than the * wrapmargin value, i.e. if the "ABC DEF " string is replaced by * something that's longer than 60 columns from the beginning of * the line, the first two commands behave as before, but the third * command gets fairly strange.) The problem is that people wrote * macros that depended on the third command NOT being affected by * wrapmargin, as in this gem which centers lines: * * map #c $mq81a ^V^[81^V^V|D`qld0:s/ / /g^V^M$p * * For compatibility reasons, we try and make it all work here. I * offer no hope that this is right, but it's probably pretty close. * * XXX * Once I work my courage up, this is all gonna go away. It's too * evil to survive. */ if ((O_ISSET(sp, O_WRAPLEN) || O_ISSET(sp, O_WRAPMARGIN)) && (!MAPPED_KEYS_WAITING(sp) || !F_ISSET(vp, VC_C1SET))) LF_SET(TXT_WRAPMARGIN); return (flags); } ================================================ FILE: vi/v_left.c ================================================ /* $OpenBSD: v_left.c,v 1.7 2022/12/26 19:16:04 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_left -- [count]^H, [count]h * Move left by columns. * * PUBLIC: int v_left(SCR *, VICMD *); */ int v_left(SCR *sp, VICMD *vp) { recno_t cnt; /* * !!! * The ^H and h commands always failed in the first column. */ if (vp->m_start.cno == 0) { v_sol(sp); return (1); } /* Find the end of the range. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (vp->m_start.cno > cnt) vp->m_stop.cno = vp->m_start.cno - cnt; else vp->m_stop.cno = 0; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } /* * v_cfirst -- [count]_ * Move to the first non-blank character in a line. * * PUBLIC: int v_cfirst(SCR *, VICMD *); */ int v_cfirst(SCR *sp, VICMD *vp) { recno_t cnt, lno; /* * !!! * If the _ is a motion component, it makes the command a line motion * e.g. "d_" deletes the line. It also means that the cursor doesn't * move. * * The _ command never failed in the first column. */ if (ISMOTION(vp)) F_SET(vp, VM_LMODE); /* * !!! * Historically a specified count makes _ move down count - 1 * rows, so, "3_" is the same as "2j_". */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt != 1) { --vp->count; return (v_down(sp, vp)); } /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case _ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The _ command has to fail if the file is empty and we're doing * a delete. If deleting line 1, and 0 is the first nonblank, * make the check. */ if (vp->m_stop.lno == 1 && vp->m_stop.cno == 0 && ISCMD(vp->rkp, 'd')) { if (db_last(sp, &lno)) return (1); if (lno == 0) { v_sol(sp); return (1); } } /* * Delete and non-motion commands move to the end of the range, * yank stays at the start. Ignore others. */ vp->m_final = ISMOTION(vp) && ISCMD(vp->rkp, 'y') ? vp->m_start : vp->m_stop; return (0); } /* * v_first -- ^ * Move to the first non-blank character in this line. * * PUBLIC: int v_first(SCR *, VICMD *); */ int v_first(SCR *sp, VICMD *vp) { /* * !!! * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ /* * Move to the first non-blank. * * Can't just use RCM_SET_FNB, in case ^ is used as the motion * component of another command. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); /* * !!! * The ^ command succeeded if used as a command when the cursor was * on the first non-blank in the line, but failed if used as a motion * component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == vp->m_stop.cno) { v_sol(sp); return (1); } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending character. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); } /* * v_ncol -- [count]| * Move to column count or the first column on this line. If the * requested column is past EOL, move to EOL. The nasty part is * that we have to know character column widths to make this work. * * PUBLIC: int v_ncol(SCR *, VICMD *); */ int v_ncol(SCR *sp, VICMD *vp) { if (F_ISSET(vp, VC_C1SET) && vp->count > 1) { --vp->count; vp->m_stop.cno = vs_colpos(sp, vp->m_start.lno, (size_t)vp->count); /* * !!! * The | command succeeded if used as a command and the cursor * didn't move, but failed if used as a motion component in the * same situation. */ if (ISMOTION(vp) && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } } else { /* * !!! * The | command succeeded if used as a command in column 0 * without a count, but failed if used as a motion component * in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (1); } vp->m_stop.cno = 0; } /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. Motion commands adjust the * ending point to the character before the current ending character. * * If moving left, all commands move to the end of the range. Motion * commands adjust the starting point to the character before the * current starting character. */ if (vp->m_start.cno < vp->m_stop.cno) if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; else { if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; } return (0); } /* * v_zero -- 0 * Move to the first column on this line. * * PUBLIC: int v_zero(SCR *, VICMD *); */ int v_zero(SCR *sp, VICMD *vp) { /* * !!! * The 0 command succeeded if used as a command in the first column * but failed if used as a motion component in the same situation. */ if (ISMOTION(vp) && vp->m_start.cno == 0) { v_sol(sp); return (1); } /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. */ vp->m_stop.cno = 0; if (ISMOTION(vp)) --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } ================================================ FILE: vi/v_mark.c ================================================ /* $OpenBSD: v_mark.c,v 1.11 2022/12/26 19:16:04 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_mark -- m[a-z] * Set a mark. * * PUBLIC: int v_mark(SCR *, VICMD *); */ int v_mark(SCR *sp, VICMD *vp) { return (mark_set(sp, vp->character, &vp->m_start, 1)); } enum which {BQMARK, FQMARK}; static int mark(SCR *, VICMD *, enum which); /* * v_bmark -- `['`a-z] * Move to a mark. * * Moves to a mark, setting both row and column. * * !!! * Although not commonly known, the "'`" and "'`" forms are historically * valid. The behavior is determined by the first character, so "`'" is * the same as "``". Remember this fact -- you'll be amazed at how many * people don't know it and will be delighted that you are able to tell * them. * * PUBLIC: int v_bmark(SCR *, VICMD *); */ int v_bmark(SCR *sp, VICMD *vp) { return (mark(sp, vp, BQMARK)); } /* * v_fmark -- '['`a-z] * Move to a mark. * * Move to the first nonblank character of the line containing the mark. * * PUBLIC: int v_fmark(SCR *, VICMD *); */ int v_fmark(SCR *sp, VICMD *vp) { return (mark(sp, vp, FQMARK)); } /* * mark -- * Mark commands. */ static int mark(SCR *sp, VICMD *vp, enum which cmd) { MARK m; size_t len; if (mark_get(sp, vp->character, &vp->m_stop, M_BERR)) return (1); /* * !!! * Historically, BQMARKS for character positions that no longer * existed acted as FQMARKS. * * FQMARKS move to the first non-blank. */ switch (cmd) { case BQMARK: if (db_get(sp, vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); if (vp->m_stop.cno < len || (vp->m_stop.cno == len && len == 0)) break; if (ISMOTION(vp)) F_SET(vp, VM_LMODE); cmd = FQMARK; /* FALLTHROUGH */ case FQMARK: vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); break; default: abort(); } /* Non-motion commands move to the end of the range. */ if (!ISMOTION(vp)) { vp->m_final = vp->m_stop; return (0); } /* * !!! * If a motion component to a BQMARK, the cursor has to move. */ if (cmd == BQMARK && vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno == vp->m_start.cno) { v_nomove(sp); return (1); } /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; } /* * Yank cursor motion, when associated with marks as motion commands, * historically behaved as follows: * * ` motion ' motion * Line change? Line change? * Y N Y N * -------------- --------------- * FORWARD: | NM NM | NM NM * | | * BACKWARD: | M M | M NM(1) * * where NM means the cursor didn't move, and M means the cursor * moved to the mark. * * As the cursor was usually moved for yank commands associated * with backward motions, this implementation regularizes it by * changing the NM at position (1) to be an M. This makes mark * motions match search motions, which is probably A Good Thing. * * Delete cursor motion was always to the start of the text region, * regardless. Ignore other motion commands. */ vp->m_final = vp->m_start; /* * Forward marks are always line oriented, and it's set in the * vcmd.c table. */ if (cmd == FQMARK) return (0); /* * BQMARK'S moving backward and starting at column 0, and ones moving * forward and ending at column 0 are corrected to the last column of * the previous line. Otherwise, adjust the starting/ending point to * the character before the current one (this is safe because we know * the search had to move to succeed). * * Mark motions become line mode operations if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); } ================================================ FILE: vi/v_match.c ================================================ /* $OpenBSD: v_match.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_match -- % * Search to matching character. * * PUBLIC: int v_match(SCR *, VICMD *); */ int v_match(SCR *sp, VICMD *vp) { VCS cs; MARK *mp; size_t cno, len, off; int cnt, isempty, matchc, startc, (*gc)(SCR *, VCS *); char *p; /* * !!! * Historic practice; ignore the count. * * !!! * Historical practice was to search for the initial character in the * forward direction only. */ if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (isempty) goto nomatch; return (1); } for (off = vp->m_start.cno;; ++off) { if (off >= len) { nomatch: msgq(sp, M_BERR, "No match character on this line"); return (1); } switch (startc = p[off]) { case '(': matchc = ')'; gc = cs_next; break; case ')': matchc = '('; gc = cs_prev; break; case '[': matchc = ']'; gc = cs_next; break; case ']': matchc = '['; gc = cs_prev; break; case '{': matchc = '}'; gc = cs_next; break; case '}': matchc = '{'; gc = cs_prev; break; case '<': matchc = '>'; gc = cs_next; break; case '>': matchc = '<'; gc = cs_prev; break; default: continue; } break; } cs.cs_lno = vp->m_start.lno; cs.cs_cno = off; if (cs_init(sp, &cs)) return (1); for (cnt = 1;;) { if (gc(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) break; continue; } if (cs.cs_ch == startc) ++cnt; else if (cs.cs_ch == matchc && --cnt == 0) break; } if (cnt) { msgq(sp, M_BERR, "Matching character not found"); return (1); } vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * If moving right, non-motion commands move to the end of the range. * Delete and yank stay at the start. * * If moving left, all commands move to the end of the range. * * !!! * Don't correct for leftward movement -- historic vi deleted the * starting cursor position when deleting to a match. */ if (vp->m_start.lno < vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno < vp->m_stop.cno)) vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; else vp->m_final = vp->m_stop; /* * !!! * If the motion is across lines, and the earliest cursor position * is at or before any non-blank characters in the line, i.e. the * movement is cutting all of the line's text, and the later cursor * position has nothing other than whitespace characters between it * and the end of its line, the buffer is in line mode. */ if (!ISMOTION(vp) || vp->m_start.lno == vp->m_stop.lno) return (0); mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_start : &vp->m_stop; if (mp->cno != 0) { cno = 0; if (nonblank(sp, mp->lno, &cno)) return (1); if (cno < mp->cno) return (0); } mp = vp->m_start.lno < vp->m_stop.lno ? &vp->m_stop : &vp->m_start; if (db_get(sp, mp->lno, DBG_FATAL, &p, &len)) return (1); for (p += mp->cno + 1, len -= mp->cno; --len; ++p) if (!isblank(*p)) return (0); F_SET(vp, VM_LMODE); return (0); } ================================================ FILE: vi/v_paragraph.c ================================================ /* $OpenBSD: v_paragraph.c,v 1.10 2023/09/07 11:17:32 tobhe Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define INTEXT_CHECK { \ if (len == 0 || v_isempty(p, len)) { \ if (!--cnt) \ goto found; \ pstate = P_INBLANK; \ } \ /* \ * !!! \ * Historic documentation (USD:15-11, 4.2) said that formfeed \ * characters (^L) in the first column delimited paragraphs. \ * The historic vi code mentions formfeed characters, but never \ * implements them. It seems reasonable, do it. \ */ \ if (p[0] == '\014') { \ if (!--cnt) \ goto found; \ if (pstate == P_INTEXT && !--cnt) \ goto found; \ continue; \ } \ if (p[0] != '.' || len < 2) \ continue; \ for (lp = VIP(sp)->ps; *lp != '\0'; lp += 2) \ if (lp[0] == p[1] && \ ((lp[1] == ' ' && len == 2) || (lp[1] == p[2]))) { \ if (!--cnt) \ goto found; \ if (pstate == P_INTEXT && !--cnt) \ goto found; \ } \ } /* * v_paragraphf -- [count]} * Move forward count paragraphs. * * Paragraphs are empty lines after text, formfeed characters, or values * from the paragraph or section options. * * PUBLIC: int v_paragraphf(SCR *, VICMD *); */ int v_paragraphf(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t lastlen, len; recno_t cnt, lastlno, lno; int isempty; char *p, *lp; /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. * * This was historical practice in vi, with a single exception. If * the paragraph movement was from the start of the last line to EOF, * then all the characters were deleted from the last line, but the * line itself remained. If somebody complains, don't pause, don't * hesitate, just hit them. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } } /* Figure out what state we're currently in. */ lno = vp->m_start.lno; if (db_get(sp, lno, 0, &p, &len)) goto eof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; } for (;;) { lastlno = lno; lastlen = len; if (db_get(sp, ++lno, 0, &p, &len)) goto eof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len == 0 || v_isempty(p, len)) break; if (--cnt) { pstate = P_INTEXT; break; } /* * !!! * Non-motion commands move to the end of the range, * delete and yank stay at the start. Ignore others. * Adjust the end of the range for motion commands; * historically, a motion component was to the end of * the previous line, whereas the movement command was * to the start of the new "paragraph". */ found: if (ISMOTION(vp)) { vp->m_stop.lno = lastlno; vp->m_stop.cno = lastlen ? lastlen - 1 : 0; vp->m_final = vp->m_start; } else { vp->m_stop.lno = lno; vp->m_stop.cno = 0; vp->m_final = vp->m_stop; } return (0); default: abort(); } } /* * !!! * Adjust end of the range for motion commands; EOF is a movement * sink. The } command historically moved to the end of the last * line, not the beginning, from any position before the end of the * last line. It also historically worked on empty files, so we * have to make it okay. */ eof: if (vp->m_start.lno == lno || vp->m_start.lno == lno - 1) { if (db_eget(sp, vp->m_start.lno, &p, &len, &isempty)) { if (!isempty) return (1); vp->m_start.cno = 0; return (0); } if (vp->m_start.cno == (len ? len - 1 : 0)) { v_eof(sp, NULL); return (1); } } /* * !!! * Non-motion commands move to the end of the range, delete * and yank stay at the start. Ignore others. * * If deleting the line (which happens if deleting to EOF), then * cursor movement is to the first nonblank. */ if (ISMOTION(vp) && ISCMD(vp->rkp, 'd')) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } vp->m_stop.lno = lno - 1; vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_paragraphb -- [count]{ * Move backward count paragraphs. * * PUBLIC: int v_paragraphb(SCR *, VICMD *); */ int v_paragraphb(SCR *sp, VICMD *vp) { enum { P_INTEXT, P_INBLANK } pstate; size_t len; recno_t cnt, lno; char *p, *lp; /* * !!! * Check for SOF. The historic vi didn't complain if users hit SOF * repeatedly, unless it was part of a motion command. There is no * question but that Emerson's editor of choice was vi. * * The { command historically moved to the beginning of the first * line if invoked on the first line. * * !!! * If the starting cursor position is in the first column (backward * paragraph movements did NOT historically pay attention to non-blank * characters) i.e. the movement is cutting the entire line, the buffer * is in line mode. Cuts from the beginning of the line also did not * cut the current line, but started at the previous EOL. * * Correct for a left motion component while we're thinking about it. */ lno = vp->m_start.lno; if (ISMOTION(vp)) { if (vp->m_start.cno == 0) { if (vp->m_start.lno == 1) { v_sof(sp, &vp->m_start); return (1); } else --vp->m_start.lno; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; } if (vp->m_start.lno <= 1) goto sof; /* Figure out what state we're currently in. */ if (db_get(sp, lno, 0, &p, &len)) goto sof; /* * If we start in text, we want to switch states * (2 * N - 1) times, in non-text, (2 * N) times. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt *= 2; if (len == 0 || v_isempty(p, len)) pstate = P_INBLANK; else { --cnt; pstate = P_INTEXT; /* * !!! * If the starting cursor is past the first column, * the current line is checked for a paragraph. */ if (vp->m_start.cno > 0) ++lno; } for (;;) { if (db_get(sp, --lno, 0, &p, &len)) goto sof; switch (pstate) { case P_INTEXT: INTEXT_CHECK; break; case P_INBLANK: if (len != 0 && !v_isempty(p, len)) { if (!--cnt) goto found; pstate = P_INTEXT; } break; default: abort(); } } /* SOF is a movement sink. */ sof: lno = 1; found: vp->m_stop.lno = lno; vp->m_stop.cno = 0; /* * All commands move to the end of the range. (We already * adjusted the start of the range for motion commands). */ vp->m_final = vp->m_stop; return (0); } /* * v_buildps -- * Build the paragraph command search pattern. * * PUBLIC: int v_buildps(SCR *, char *, char *); */ int v_buildps(SCR *sp, char *p_p, char *s_p) { VI_PRIVATE *vip; size_t p_len, s_len; char *p; /* * The vi paragraph command searches for either a paragraph or * section option macro. */ p_len = p_p == NULL ? 0 : strlen(p_p); s_len = s_p == NULL ? 0 : strlen(s_p); if (p_len == 0 && s_len == 0) return (0); MALLOC_RET(sp, p, p_len + s_len + 1); vip = VIP(sp); free(vip->ps); if (p_p != NULL) memmove(p, p_p, p_len + 1); if (s_p != NULL) memmove(p + p_len, s_p, s_len + 1); vip->ps = p; return (0); } ================================================ FILE: vi/v_put.c ================================================ /* $OpenBSD: v_put.c,v 1.9 2025/08/23 21:02:10 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static void inc_buf(SCR *, VICMD *); /* * v_Put -- [buffer]P * Insert the contents of the buffer before the cursor. * * PUBLIC: int v_Put(SCR *, VICMD *); */ int v_Put(SCR *sp, VICMD *vp) { unsigned long cnt; if (F_ISSET(vp, VC_ISDOT)) inc_buf(sp, vp); /* * !!! * Historic vi did not support a count with the 'p' and 'P' * commands. It's useful, so we do. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_final, 0, cnt)) return (1); return (0); } /* * v_put -- [buffer]p * Insert the contents of the buffer after the cursor. * * PUBLIC: int v_put(SCR *, VICMD *); */ int v_put(SCR *sp, VICMD *vp) { unsigned long cnt; if (F_ISSET(vp, VC_ISDOT)) inc_buf(sp, vp); /* * !!! * Historic vi did not support a count with the 'p' and 'P' * commands. It's useful, so we do. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (put(sp, NULL, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_final, 1, cnt)) return (1); return (0); } /* * !!! * Historical whackadoo. The dot command `puts' the numbered buffer * after the last one put. For example, `"4p.' would put buffer #4 * and buffer #5. If the user continued to enter '.', the #9 buffer * would be repeatedly output. This was not documented, and is a bit * tricky to reconstruct. Historical versions of vi also dropped the * contents of the default buffer after each put, so after `"4p' the * default buffer would be empty. This makes no sense to me, so we * don't bother. Don't assume sequential order of numeric characters. * * And, if that weren't exciting enough, failed commands don't normally * set the dot command. Well, boys and girls, an exception is that * the buffer increment gets done regardless of the success of the put. */ static void inc_buf(SCR *sp, VICMD *vp) { CHAR_T v; switch (vp->buffer) { case '1': v = '2'; break; case '2': v = '3'; break; case '3': v = '4'; break; case '4': v = '5'; break; case '5': v = '6'; break; case '6': v = '7'; break; case '7': v = '8'; break; case '8': v = '9'; break; default: return; } VIP(sp)->sdot.buffer = vp->buffer = v; } ================================================ FILE: vi/v_redraw.c ================================================ /* $OpenBSD: v_redraw.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_redraw -- ^L, ^R * Redraw the screen. * * PUBLIC: int v_redraw(SCR *, VICMD *); */ int v_redraw(SCR *sp, VICMD *vp) { F_SET(sp, SC_SCR_REFORMAT); return (sp->gp->scr_refresh(sp, 1)); } ================================================ FILE: vi/v_replace.c ================================================ /* $OpenBSD: v_replace.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_replace -- [count]r * * !!! * The r command in historic vi was almost beautiful in its badness. For * example, "r" and "r" beeped the terminal and deleted * a single character. "Nr", where N was greater than 1, * inserted a single carriage return. "r" did cancel the command, * but "r" erased a single character. To enter a literal * character, it required three characters after the * command. This may not be right, but at least it's not insane. * * PUBLIC: int v_replace(SCR *, VICMD *); */ int v_replace(SCR *sp, VICMD *vp) { EVENT ev; VI_PRIVATE *vip; TEXT *tp; size_t blen, len; unsigned long cnt; int quote, rval; char *bp, *p; vip = VIP(sp); /* * If the line doesn't exist, or it's empty, replacement isn't * allowed. It's not hard to implement, but: * * 1: It's historic practice (vi beeped before the replacement * character was even entered). * 2: For consistency, this change would require that the more * general case, "Nr", when the user is < N characters from * the end of the line, also work, which would be a bit odd. * 3: Replacing with a has somewhat odd semantics. */ if (db_get(sp, vp->m_start.lno, DBG_FATAL, &p, &len)) return (1); if (len == 0) { msgq(sp, M_BERR, "No characters to replace"); return (1); } /* * Figure out how many characters to be replace. For no particular * reason (other than that the semantics of replacing the newline * are confusing) only permit the replacement of the characters in * the current line. I suppose we could append replacement characters * to the line, but I see no compelling reason to do so. Check this * before we get the character to match historic practice, where Nr * failed immediately if there were less than N characters from the * cursor to the end of the line. */ cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; vp->m_stop.lno = vp->m_start.lno; vp->m_stop.cno = vp->m_start.cno + cnt - 1; if (vp->m_stop.cno > len - 1) { v_eol(sp, &vp->m_start); return (1); } /* * If it's not a repeat, reset the current mode and get a replacement * character. */ quote = 0; if (!F_ISSET(vp, VC_ISDOT)) { sp->showmode = SM_REPLACE; if (vs_refresh(sp, 0)) return (1); next: if (v_event_get(sp, &ev, 0, 0)) return (1); switch (ev.e_event) { case E_CHARACTER: /* * means escape the next character. * means they changed their minds. */ if (!quote) { if (ev.e_value == K_VLNEXT) { quote = 1; goto next; } if (ev.e_value == K_ESCAPE) return (0); } vip->rlast = ev.e_c; vip->rvalue = ev.e_value; break; case E_ERR: case E_EOF: F_SET(sp, SC_EXIT_FORCE); return (1); case E_INTERRUPT: /* means they changed their minds. */ return (0); case E_WRESIZE: /* interrupts the input mode. */ sp->gp->scr_imctrl(sp, IMCTRL_OFF); v_emsg(sp, NULL, VIM_WRESIZE); return (0); case E_REPAINT: if (vs_repaint(sp, &ev)) return (1); goto next; default: v_event_err(sp, &ev); return (0); } } /* Copy the line. */ GET_SPACE_RET(sp, bp, blen, len); memmove(bp, p, len); p = bp; /* * Versions of nvi before 1.57 created N new lines when they replaced * N characters with or characters. This * is different from the historic vi, which replaced N characters with * a single new line. Users complained, so we match historic practice. */ if ((!quote && vip->rvalue == K_CR) || vip->rvalue == K_NL) { /* Set return line. */ vp->m_stop.lno = vp->m_start.lno + 1; vp->m_stop.cno = 0; /* The first part of the current line. */ if (db_set(sp, vp->m_start.lno, p, vp->m_start.cno)) goto err_ret; /* * The rest of the current line. And, of course, now it gets * tricky. If there are characters left in the line and if * the autoindent edit option is set, white space after the * replaced character is discarded, autoindent is applied, and * the cursor moves to the last indent character. */ p += vp->m_start.cno + cnt; len -= vp->m_start.cno + cnt; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) for (; len && isblank(*p); --len, ++p); if ((tp = text_init(sp, p, len, len)) == NULL) goto err_ret; if (len != 0 && O_ISSET(sp, O_AUTOINDENT)) { if (v_txt_auto(sp, vp->m_start.lno, NULL, 0, tp)) goto err_ret; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; } else vp->m_stop.cno = 0; vp->m_stop.cno = tp->ai ? tp->ai - 1 : 0; if (db_append(sp, 1, vp->m_start.lno, tp->lb, tp->len)) err_ret: rval = 1; else { text_free(tp); rval = 0; } } else { memset(bp + vp->m_start.cno, vip->rlast, cnt); rval = db_set(sp, vp->m_start.lno, bp, len); } FREE_SPACE(sp, bp, blen); vp->m_final = vp->m_stop; return (rval); } ================================================ FILE: vi/v_right.c ================================================ /* $OpenBSD: v_right.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_right -- [count]' ', [count]l * Move right by columns. * * PUBLIC: int v_right(SCR *, VICMD *); */ int v_right(SCR *sp, VICMD *vp) { size_t len; int isempty; if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { if (isempty) goto eol; return (1); } /* It's always illegal to move right on empty lines. */ if (len == 0) { eol: v_eol(sp, NULL); return (1); } /* * Non-motion commands move to the end of the range. Delete and * yank stay at the start. Ignore others. Adjust the end of the * range for motion commands. * * !!! * Historically, "[cdsy]l" worked at the end of a line. Also, * EOL is a count sink. */ vp->m_stop.cno = vp->m_start.cno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (vp->m_start.cno == len - 1 && !ISMOTION(vp)) { v_eol(sp, NULL); return (1); } if (vp->m_stop.cno >= len) { vp->m_stop.cno = len - 1; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; } else if (ISMOTION(vp)) { --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); } /* * v_dollar -- [count]$ * Move to the last column. * * PUBLIC: int v_dollar(SCR *, VICMD *); */ int v_dollar(SCR *sp, VICMD *vp) { size_t len; int isempty; /* * !!! * A count moves down count - 1 rows, so, "3$" is the same as "2j$". */ if ((F_ISSET(vp, VC_C1SET) ? vp->count : 1) != 1) { /* * !!! * Historically, if the $ is a motion, and deleting from * at or before the first non-blank of the line, it's a * line motion, and the line motion flag is set. */ vp->m_stop.cno = 0; if (nonblank(sp, vp->m_start.lno, &vp->m_stop.cno)) return (1); if (ISMOTION(vp) && vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); --vp->count; if (v_down(sp, vp)) return (1); } /* * !!! * Historically, it was illegal to use $ as a motion command on * an empty line. Unfortunately, even though C was historically * aliased to c$, it (and not c$) was special cased to work on * empty lines. Since we alias C to c$ too, we have a problem. * To fix it, we let c$ go through, on the assumption that it's * not a problem for it to work. */ if (db_eget(sp, vp->m_stop.lno, NULL, &len, &isempty)) { if (!isempty) return (1); len = 0; } if (len == 0) { if (ISMOTION(vp) && !ISCMD(vp->rkp, 'c')) { v_eol(sp, NULL); return (1); } return (0); } /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start. Ignore others. */ vp->m_stop.cno = len ? len - 1 : 0; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } ================================================ FILE: vi/v_screen.c ================================================ /* $OpenBSD: v_screen.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_screen -- ^W * Switch screens. * * PUBLIC: int v_screen(SCR *, VICMD *); */ int v_screen(SCR *sp, VICMD *vp) { /* * You can't leave a colon command-line edit window -- it's not that * it won't work, but it gets real weird, real fast when you execute * a colon command out of a window that was forked from a window that's * now backgrounded... You get the idea. */ if (F_ISSET(sp, SC_COMEDIT)) { msgq(sp, M_ERR, "Enter to execute a command, :q to exit"); return (1); } /* * Try for the next lower screen, or, go back to the first * screen on the stack. */ if (TAILQ_NEXT(sp, q)) sp->nextdisp = TAILQ_NEXT(sp, q); else if (TAILQ_FIRST(&sp->gp->dq) == sp) { msgq(sp, M_ERR, "No other screen to switch to"); return (1); } else sp->nextdisp = TAILQ_FIRST(&sp->gp->dq); F_SET(sp->nextdisp, SC_STATUS); F_SET(sp, SC_SSWITCH | SC_STATUS); return (0); } ================================================ FILE: vi/v_scroll.c ================================================ /* $OpenBSD: v_scroll.c,v 1.10 2015/01/16 06:40:14 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) static void goto_adjust(VICMD *); /* * The historic vi had a problem in that all movements were by physical * lines, not by logical, or screen lines. Arguments can be made that this * is the right thing to do. For example, single line movements, such as * 'j' or 'k', should probably work on physical lines. Commands like "dj", * or "j.", where '.' is a change command, make more sense for physical lines * than they do for logical lines. * * These arguments, however, don't apply to scrolling commands like ^D and * ^F -- if the window is fairly small, using physical lines can result in * a half-page scroll repainting the entire screen, which is not what the * user wanted. Second, if the line is larger than the screen, using physical * lines can make it impossible to display parts of the line -- there aren't * any commands that don't display the beginning of the line in historic vi, * and if both the beginning and end of the line can't be on the screen at * the same time, you lose. This is even worse in the case of the H, L, and * M commands -- for large lines, they may all refer to the same line and * will result in no movement at all. * * Another issue is that page and half-page scrolling commands historically * moved to the first non-blank character in the new line. If the line is * approximately the same size as the screen, this loses because the cursor * before and after a ^D, may refer to the same location on the screen. In * this implementation, scrolling commands set the cursor to the first non- * blank character if the line changes because of the scroll. Otherwise, * the cursor is left alone. * * This implementation does the scrolling (^B, ^D, ^F, ^U, ^Y, ^E), and the * cursor positioning commands (H, L, M) commands using logical lines, not * physical. */ /* * v_lgoto -- [count]G * Go to first non-blank character of the line count, the last line * of the file by default. * * PUBLIC: int v_lgoto(SCR *, VICMD *); */ int v_lgoto(SCR *sp, VICMD *vp) { recno_t nlines; if (F_ISSET(vp, VC_C1SET)) { if (!db_exist(sp, vp->count)) { /* * !!! * Historically, 1G was legal in an empty file. */ if (vp->count == 1) { if (db_last(sp, &nlines)) return (1); if (nlines == 0) return (0); } v_eof(sp, &vp->m_start); return (1); } vp->m_stop.lno = vp->count; } else { if (db_last(sp, &nlines)) return (1); vp->m_stop.lno = nlines ? nlines : 1; } goto_adjust(vp); return (0); } /* * v_home -- [count]H * Move to the first non-blank character of the logical line * count - 1 from the top of the screen, 0 by default. * * PUBLIC: int v_home(SCR *, VICMD *); */ int v_home(SCR *sp, VICMD *vp) { if (vs_sm_position(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_TOP)) return (1); goto_adjust(vp); return (0); } /* * v_middle -- M * Move to the first non-blank character of the logical line * in the middle of the screen. * * PUBLIC: int v_middle(SCR *, VICMD *); */ int v_middle(SCR *sp, VICMD *vp) { /* * Yielding to none in our quest for compatibility with every * historical blemish of vi, no matter how strange it might be, * we permit the user to enter a count and then ignore it. */ if (vs_sm_position(sp, &vp->m_stop, 0, P_MIDDLE)) return (1); goto_adjust(vp); return (0); } /* * v_bottom -- [count]L * Move to the first non-blank character of the logical line * count - 1 from the bottom of the screen, 0 by default. * * PUBLIC: int v_bottom(SCR *, VICMD *); */ int v_bottom(SCR *sp, VICMD *vp) { if (vs_sm_position(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count - 1 : 0, P_BOTTOM)) return (1); goto_adjust(vp); return (0); } static void goto_adjust(VICMD *vp) { /* Guess that it's the end of the range. */ vp->m_final = vp->m_stop; /* * Non-motion commands move the cursor to the end of the range, and * then to the NEXT nonblank of the line. Historic vi always moved * to the first nonblank in the line; since the H, M, and L commands * are logical motions in this implementation, we do the next nonblank * so that it looks approximately the same to the user. To make this * happen, the VM_RCM_SETNNB flag is set in the vcmd.c command table. * * If it's a motion, it's more complicated. The best possible solution * is probably to display the first nonblank of the line the cursor * will eventually rest on. This is tricky, particularly given that if * the associated command is a delete, we don't yet know what line that * will be. So, we clear the VM_RCM_SETNNB flag, and set the first * nonblank flag (VM_RCM_SETFNB). Note, if the lines are sufficiently * long, this can cause the cursor to warp out of the screen. It's too * hard to fix. * * XXX * The G command is always first nonblank, so it's okay to reset it. */ if (ISMOTION(vp)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } else return; /* * If moving backward in the file, delete and yank move to the end * of the range, unless the line didn't change, in which case yank * doesn't move. If moving forward in the file, delete and yank * stay at the start of the range. Ignore others. */ if (vp->m_stop.lno < vp->m_start.lno || (vp->m_stop.lno == vp->m_start.lno && vp->m_stop.cno < vp->m_start.cno)) { if (ISCMD(vp->rkp, 'y') && vp->m_stop.lno == vp->m_start.lno) vp->m_final = vp->m_start; } else vp->m_final = vp->m_start; } /* * v_up -- [count]^P, [count]k, [count]- * Move up by lines. * * PUBLIC: int v_up(SCR *, VICMD *); */ int v_up(SCR *sp, VICMD *vp) { recno_t lno; lno = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (vp->m_start.lno <= lno) { v_sof(sp, &vp->m_start); return (1); } vp->m_stop.lno = vp->m_start.lno - lno; vp->m_final = vp->m_stop; return (0); } /* * v_cr -- [count]^M * In a script window, send the line to the shell. * In a regular window, move down by lines. * * PUBLIC: int v_cr(SCR *, VICMD *); */ int v_cr(SCR *sp, VICMD *vp) { /* If it's a colon command-line edit window, it's an ex command. */ if (F_ISSET(sp, SC_COMEDIT)) return (v_ecl_exec(sp)); /* If it's a script window, exec the line. */ if (F_ISSET(sp, SC_SCRIPT)) return (sscr_exec(sp, vp->m_start.lno)); /* Otherwise, it's the same as v_down(). */ return (v_down(sp, vp)); } /* * v_down -- [count]^J, [count]^N, [count]j, [count]^M, [count]+ * Move down by lines. * * PUBLIC: int v_down(SCR *, VICMD *); */ int v_down(SCR *sp, VICMD *vp) { recno_t lno; lno = vp->m_start.lno + (F_ISSET(vp, VC_C1SET) ? vp->count : 1); if (!db_exist(sp, lno)) { v_eof(sp, &vp->m_start); return (1); } vp->m_stop.lno = lno; vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_hpageup -- [count]^U * Page up half screens. * * PUBLIC: int v_hpageup(SCR *, VICMD *); */ int v_hpageup(SCR *sp, VICMD *vp) { /* * Half screens always succeed unless already at SOF. * * !!! * Half screens set the scroll value, even if the command * ultimately failed, in historic vi. Probably a don't care. */ if (F_ISSET(vp, VC_C1SET)) sp->defscroll = vp->count; if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_U)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_hpagedown -- [count]^D * Page down half screens. * * PUBLIC: int v_hpagedown(SCR *, VICMD *); */ int v_hpagedown(SCR *sp, VICMD *vp) { /* * Half screens always succeed unless already at EOF. * * !!! * Half screens set the scroll value, even if the command * ultimately failed, in historic vi. Probably a don't care. */ if (F_ISSET(vp, VC_C1SET)) sp->defscroll = vp->count; if (vs_sm_scroll(sp, &vp->m_stop, sp->defscroll, CNTRL_D)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_pagedown -- [count]^F * Page down full screens. * !!! * Historic vi did not move to the EOF if the screen couldn't move, i.e. * if EOF was already displayed on the screen. This implementation does * move to EOF in that case, making ^F more like the historic ^D. * * PUBLIC: int v_pagedown(SCR *, VICMD *); */ int v_pagedown(SCR *sp, VICMD *vp) { recno_t offset; /* * !!! * The calculation in IEEE Std 1003.2-1992 (POSIX) is: * * top_line = top_line + count * (window - 2); * * which was historically wrong. The correct one is: * * top_line = top_line + count * window - 2; * * i.e. the two line "overlap" was only subtracted once. Which * makes no sense, but then again, an overlap makes no sense for * any screen but the "next" one anyway. We do it the historical * way as there's no good reason to change it. * * If the screen has been split, use the smaller of the current * window size and the window option value. * * It possible for this calculation to be less than 1; move at * least one line. */ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? MINIMUM(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); offset = offset <= 2 ? 1 : offset - 2; if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_F)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_pageup -- [count]^B * Page up full screens. * * !!! * Historic vi did not move to the SOF if the screen couldn't move, i.e. * if SOF was already displayed on the screen. This implementation does * move to SOF in that case, making ^B more like the historic ^U. * * PUBLIC: int v_pageup(SCR *, VICMD *); */ int v_pageup(SCR *sp, VICMD *vp) { recno_t offset; /* * !!! * The calculation in IEEE Std 1003.2-1992 (POSIX) is: * * top_line = top_line - count * (window - 2); * * which was historically wrong. The correct one is: * * top_line = (top_line - count * window) + 2; * * A simpler expression is that, as with ^F, we scroll exactly: * * count * window - 2 * * lines. * * Bizarre. As with ^F, an overlap makes no sense for anything * but the first screen. We do it the historical way as there's * no good reason to change it. * * If the screen has been split, use the smaller of the current * window size and the window option value. * * It possible for this calculation to be less than 1; move at * least one line. */ offset = (F_ISSET(vp, VC_C1SET) ? vp->count : 1) * (IS_SPLIT(sp) ? MINIMUM(sp->t_maxrows, O_VAL(sp, O_WINDOW)) : O_VAL(sp, O_WINDOW)); offset = offset <= 2 ? 1 : offset - 2; if (vs_sm_scroll(sp, &vp->m_stop, offset, CNTRL_B)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_lineup -- [count]^Y * Page up by lines. * * PUBLIC: int v_lineup(SCR *, VICMD *); */ int v_lineup(SCR *sp, VICMD *vp) { /* * The cursor moves down, staying with its original line, unless it * reaches the bottom of the screen. */ if (vs_sm_scroll(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_Y)) return (1); vp->m_final = vp->m_stop; return (0); } /* * v_linedown -- [count]^E * Page down by lines. * * PUBLIC: int v_linedown(SCR *, VICMD *); */ int v_linedown(SCR *sp, VICMD *vp) { /* * The cursor moves up, staying with its original line, unless it * reaches the top of the screen. */ if (vs_sm_scroll(sp, &vp->m_stop, F_ISSET(vp, VC_C1SET) ? vp->count : 1, CNTRL_E)) return (1); vp->m_final = vp->m_stop; return (0); } ================================================ FILE: vi/v_search.c ================================================ /* $OpenBSD: v_search.c,v 1.14 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int v_exaddr(SCR *, VICMD *, dir_t); static int v_search(SCR *, VICMD *, char *, size_t, unsigned int, dir_t); /* * v_srch -- [count]?RE[? offset] * Ex address search backward. * * PUBLIC: int v_searchb(SCR *, VICMD *); */ int v_searchb(SCR *sp, VICMD *vp) { return (v_exaddr(sp, vp, BACKWARD)); } /* * v_searchf -- [count]/RE[/ offset] * Ex address search forward. * * PUBLIC: int v_searchf(SCR *, VICMD *); */ int v_searchf(SCR *sp, VICMD *vp) { return (v_exaddr(sp, vp, FORWARD)); } /* * v_exaddr -- * Do a vi search (which is really an ex address). */ static int v_exaddr(SCR *sp, VICMD *vp, dir_t dir) { static EXCMDLIST fake = { "search", NULL, 0, NULL, NULL, NULL }; EXCMD *cmdp; GS *gp; TEXT *tp; recno_t s_lno; size_t len, s_cno, tlen; int err, nb, type; char *cmd, *t, buf[20]; /* * !!! * If using the search command as a motion, any addressing components * are lost, i.e. y/ptrn/+2, when repeated, is the same as y/ptrn/. */ if (F_ISSET(vp, VC_ISDOT)) return (v_search(sp, vp, NULL, 0, SEARCH_PARSE | SEARCH_MSG | SEARCH_SET, dir)); /* Get the search pattern. */ if (v_tcmd(sp, vp, dir == BACKWARD ? CH_BSEARCH : CH_FSEARCH, TXT_BS | TXT_CR | TXT_ESCAPE | TXT_PROMPT | (O_ISSET(sp, O_SEARCHINCR) ? TXT_SEARCHINCR : 0))) return (1); tp = TAILQ_FIRST(&sp->tiq); /* If the user backspaced over the prompt, do nothing. */ if (tp->term == TERM_BS) return (1); /* * If the user was doing an incremental search, then we've already * updated the cursor and moved to the right location. Return the * correct values, we're done. */ if (tp->term == TERM_SEARCH) { vp->m_stop.lno = sp->lno; vp->m_stop.cno = sp->cno; if (ISMOTION(vp)) return (v_correct(sp, vp, 0)); vp->m_final = vp->m_stop; return (0); } /* * If the user entered or , the length is * 1 and the right thing will happen, i.e. the prompt will be used * as a command character. * * Build a fake ex command structure. */ gp = sp->gp; gp->excmd.cp = tp->lb; gp->excmd.clen = tp->len; F_INIT(&gp->excmd, E_VISEARCH); /* * XXX * Warn if the search wraps. This is a pretty special case, but it's * nice feature that wasn't in the original implementations of ex/vi. * (It was added at some point to System V's version.) This message * is only displayed if there are no keys in the queue. The problem is * the command is going to succeed, and the message is informational, * not an error. If a macro displays it repeatedly, e.g., the pattern * only occurs once in the file and wrapscan is set, you lose big. For * example, if the macro does something like: * * :map K /pattern/^MjK * * Each search will display the message, but the following "/pattern/" * will immediately overwrite it, with strange results. The System V * vi displays the "wrapped" message multiple times, but because it's * overwritten each time, it's not as noticeable. As we don't discard * messages, it's a real problem for us. */ if (!KEYS_WAITING(sp)) F_SET(&gp->excmd, E_SEARCH_WMSG); /* Save the current line/column. */ s_lno = sp->lno; s_cno = sp->cno; /* * !!! * Historically, vi / and ? commands were full-blown ex addresses, * including ';' delimiters, trailing 's, multiple search * strings (separated by semi-colons) and, finally, full-blown z * commands after the / and ? search strings. (If the search was * being used as a motion, the trailing z command was ignored. * Also, we do some argument checking on the z command, to be sure * that it's not some other random command.) For multiple search * strings, leading 's at the second and subsequent strings * were eaten as well. This has some (unintended?) side-effects: * the command /ptrn/;3 is legal and results in moving to line 3. * I suppose you could use it to optionally move to line 3... * * !!! * Historically, if any part of the search command failed, the cursor * remained unmodified (even if ; was used). We have to play games * because the underlying ex parser thinks we're modifying the cursor * as we go, but I think we're compatible with historic practice. * * !!! * Historically, the command "/STRING/; " failed, apparently it * confused the parser. We're not that compatible. */ cmdp = &gp->excmd; if (ex_range(sp, cmdp, &err)) return (1); /* * Remember where any remaining command information is, and clean * up the fake ex command. */ cmd = cmdp->cp; len = cmdp->clen; gp->excmd.clen = 0; if (err) goto err2; /* Copy out the new cursor position and make sure it's okay. */ switch (cmdp->addrcnt) { case 1: vp->m_stop = cmdp->addr1; break; case 2: vp->m_stop = cmdp->addr2; break; } if (!db_exist(sp, vp->m_stop.lno)) { ex_badaddr(sp, &fake, vp->m_stop.lno == 0 ? A_ZERO : A_EOF, NUM_OK); goto err2; } /* * !!! * Historic practice is that a trailing 'z' was ignored if it was a * motion command. Should probably be an error, but not worth the * effort. */ if (ISMOTION(vp)) return (v_correct(sp, vp, F_ISSET(cmdp, E_DELTA))); /* * !!! * Historically, if it wasn't a motion command, a delta in the search * pattern turns it into a first nonblank movement. */ nb = F_ISSET(cmdp, E_DELTA); /* Check for the 'z' command. */ if (len != 0) { if (*cmd != 'z') goto err1; /* No blanks, just like the z command. */ for (t = cmd + 1, tlen = len - 1; tlen > 0; ++t, --tlen) if (!isdigit(*t)) break; if (tlen && (*t == '-' || *t == '.' || *t == '+' || *t == '^')) { ++t; --tlen; type = 1; } else type = 0; if (tlen) goto err1; /* The z command will do the nonblank for us. */ nb = 0; /* Default to z+. */ if (!type && v_event_push(sp, NULL, "+", 1, CH_NOMAP | CH_QUOTED)) return (1); /* Push the user's command. */ if (v_event_push(sp, NULL, cmd, len, CH_NOMAP | CH_QUOTED)) return (1); /* Push line number so get correct z display. */ tlen = snprintf(buf, sizeof(buf), "%lu", (unsigned long)vp->m_stop.lno); if (tlen >= sizeof(buf)) tlen = sizeof(buf) - 1; if (v_event_push(sp, NULL, buf, tlen, CH_NOMAP | CH_QUOTED)) return (1); /* Don't refresh until after 'z' happens. */ F_SET(VIP(sp), VIP_S_REFRESH); } /* Non-motion commands move to the end of the range. */ vp->m_final = vp->m_stop; if (nb) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); } return (0); err1: msgq(sp, M_ERR, "Characters after search string, line offset and/or z command"); err2: vp->m_final.lno = s_lno; vp->m_final.cno = s_cno; return (1); } /* * v_searchN -- N * Reverse last search. * * PUBLIC: int v_searchN(SCR *, VICMD *); */ int v_searchN(SCR *sp, VICMD *vp) { dir_t dir; switch (sp->searchdir) { case BACKWARD: dir = FORWARD; break; case FORWARD: dir = BACKWARD; break; default: dir = sp->searchdir; break; } return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, dir)); } /* * v_searchn -- n * Repeat last search. * * PUBLIC: int v_searchn(SCR *, VICMD *); */ int v_searchn(SCR *sp, VICMD *vp) { return (v_search(sp, vp, NULL, 0, SEARCH_PARSE, sp->searchdir)); } /* * v_searchw -- [count]^A * Search for the word under the cursor. * * PUBLIC: int v_searchw(SCR *, VICMD *); */ int v_searchw(SCR *sp, VICMD *vp) { size_t blen, len; int rval; char *bp; len = VIP(sp)->klen + sizeof(RE_WSTART) + sizeof(RE_WSTOP); GET_SPACE_RET(sp, bp, blen, len); len = snprintf(bp, blen, "%s%s%s", RE_WSTART, VIP(sp)->keyw, RE_WSTOP); if (len >= blen) len = blen - 1; rval = v_search(sp, vp, bp, len, SEARCH_SET, FORWARD); FREE_SPACE(sp, bp, blen); return (rval); } /* * v_search -- * The search commands. */ static int v_search(SCR *sp, VICMD *vp, char *ptrn, size_t plen, unsigned int flags, dir_t dir) { /* Display messages. */ LF_SET(SEARCH_MSG); /* If it's a motion search, offset past end-of-line is okay. */ if (ISMOTION(vp)) LF_SET(SEARCH_EOL); /* * XXX * Warn if the search wraps. See the comment above, in v_exaddr(). */ if (!KEYS_WAITING(sp)) LF_SET(SEARCH_WMSG); switch (dir) { case BACKWARD: if (b_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case FORWARD: if (f_search(sp, &vp->m_start, &vp->m_stop, ptrn, plen, NULL, flags)) return (1); break; case NOTSET: msgq(sp, M_ERR, "No previous search pattern"); return (1); default: abort(); } /* Correct motion commands, otherwise, simply move to the location. */ if (ISMOTION(vp)) { if (v_correct(sp, vp, 0)) return(1); } else vp->m_final = vp->m_stop; return (0); } /* * v_correct -- * Handle command with a search as the motion. * * !!! * Historically, commands didn't affect the line searched to/from if the * motion command was a search and the final position was the start/end * of the line. There were some special cases and vi was not consistent; * it was fairly easy to confuse it. For example, given the two lines: * * abcdefghi * ABCDEFGHI * * placing the cursor on the 'A' and doing y?$ would so confuse it that 'h' * 'k' and put would no longer work correctly. In any case, we try to do * the right thing, but it's not going to exactly match historic practice. * * PUBLIC: int v_correct(SCR *, VICMD *, int); */ int v_correct(SCR *sp, VICMD *vp, int isdelta) { MARK m; size_t len; /* * !!! * We may have wrapped if wrapscan was set, and we may have returned * to the position where the cursor started. Historic vi didn't cope * with this well. Yank wouldn't beep, but the first put after the * yank would move the cursor right one column (without adding any * text) and the second would put a copy of the current line. The * change and delete commands would beep, but would leave the cursor * on the colon command line. I believe that there are macros that * depend on delete, at least, failing. For now, commands that use * search as a motion component fail when the search returns to the * original cursor position. */ if (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno == vp->m_stop.cno) { msgq(sp, M_BERR, "Search wrapped to original position"); return (1); } /* * !!! * Searches become line mode operations if there was a delta specified * to the search pattern. */ if (isdelta) F_SET(vp, VM_LMODE); /* * If the motion is in the reverse direction, switch the start and * stop MARK's so that it's in a forward direction. (There's no * reason for this other than to make the tests below easier. The * code in vi.c:vi() would have done the switch.) Both forward * and backward motions can happen for any kind of search command * because of the wrapscan option. */ if (vp->m_start.lno > vp->m_stop.lno || (vp->m_start.lno == vp->m_stop.lno && vp->m_start.cno > vp->m_stop.cno)) { m = vp->m_start; vp->m_start = vp->m_stop; vp->m_stop = m; } /* * BACKWARD: * Delete and yank commands move to the end of the range. * Ignore others. * * FORWARD: * Delete and yank commands don't move. Ignore others. */ vp->m_final = vp->m_start; /* * !!! * Delta'd searches don't correct based on column positions. */ if (isdelta) return (0); /* * !!! * Backward searches starting at column 0, and forward searches ending * at column 0 are corrected to the last column of the previous line. * Otherwise, adjust the starting/ending point to the character before * the current one (this is safe because we know the search had to move * to succeed). * * Searches become line mode operations if they start at the first * nonblank and end at column 0 of another line. */ if (vp->m_start.lno < vp->m_stop.lno && vp->m_stop.cno == 0) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; len = 0; if (nonblank(sp, vp->m_start.lno, &len)) return (1); if (vp->m_start.cno <= len) F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; return (0); } ================================================ FILE: vi/v_section.c ================================================ /* $OpenBSD: v_section.c,v 1.7 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * In historic vi, the section commands ignored empty lines, unlike the * paragraph commands, which was probably okay. However, they also moved * to the start of the last line when there where no more sections instead * of the end of the last line like the paragraph commands. I've changed * the latter behavior to match the paragraph commands. * * In historic vi, a section was defined as the first character(s) of the * line matching, which could be followed by anything. This implementation * follows that historic practice. * * !!! * The historic vi documentation (USD:15-10) claimed: * The section commands interpret a preceding count as a different * window size in which to redraw the screen at the new location, * and this window size is the base size for newly drawn windows * until another size is specified. This is very useful if you are * on a slow terminal ... * * I can't get the 4BSD vi to do this, it just beeps at me. For now, a * count to the section commands simply repeats the command. */ /* * v_sectionf -- [count]]] * Move forward count sections/functions. * * !!! * Using ]] as a motion command was a bit special, historically. It could * match } as well as the usual { and section values. If it matched a { or * a section, it did NOT include the matched line. If it matched a }, it * did include the line. No clue why. * * PUBLIC: int v_sectionf(SCR *, VICMD *); */ int v_sectionf(SCR *sp, VICMD *vp) { recno_t cnt, lno; size_t len; char *p, *list, *lp; /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); /* * !!! * If the starting cursor position is at or before any non-blank * characters in the line, i.e. the movement is cutting all of the * line's text, the buffer is in line mode. It's a lot easier to * check here, because we know that the end is going to be the start * or end of a line. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0) F_SET(vp, VM_LMODE); else { vp->m_stop = vp->m_start; vp->m_stop.cno = 0; if (nonblank(sp, vp->m_stop.lno, &vp->m_stop.cno)) return (1); if (vp->m_start.cno <= vp->m_stop.cno) F_SET(vp, VM_LMODE); } } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, ++lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{' || (ISMOTION(vp) && p[0] == '}')) { if (!--cnt) { if (p[0] == '{') goto adjust1; goto adjust2; } continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { /* * !!! * If not cutting this line, adjust to the end * of the previous one. Otherwise, position to * column 0. */ adjust1: if (ISMOTION(vp)) goto ret1; adjust2: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret2; } } /* If moving forward, reached EOF, check to see if we started there. */ if (vp->m_start.lno == lno - 1) { v_eof(sp, NULL); return (1); } ret1: if (db_get(sp, --lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.lno = lno; vp->m_stop.cno = len ? len - 1 : 0; /* * Non-motion commands go to the end of the range. Delete and * yank stay at the start of the range. Ignore others. */ ret2: if (ISMOTION(vp)) { vp->m_final = vp->m_start; if (F_ISSET(vp, VM_LMODE)) vp->m_final.cno = 0; } else vp->m_final = vp->m_stop; return (0); } /* * v_sectionb -- [count][[ * Move backward count sections/functions. * * PUBLIC: int v_sectionb(SCR *, VICMD *); */ int v_sectionb(SCR *sp, VICMD *vp) { size_t len; recno_t cnt, lno; char *p, *list, *lp; /* An empty file or starting from line 1 is always illegal. */ if (vp->m_start.lno <= 1) { v_sof(sp, NULL); return (1); } /* Get the macro list. */ if ((list = O_STR(sp, O_SECTIONS)) == NULL) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; for (lno = vp->m_start.lno; !db_get(sp, --lno, 0, &p, &len);) { if (len == 0) continue; if (p[0] == '{') { if (!--cnt) goto adjust1; continue; } /* * !!! * Historic documentation (USD:15-11, 4.2) said that formfeed * characters (^L) in the first column delimited sections. * The historic code mentions formfeed characters, but never * implements them. Seems reasonable, do it. */ if (p[0] == '\014') { if (!--cnt) goto adjust1; continue; } if (p[0] != '.' || len < 2) continue; for (lp = list; *lp != '\0'; lp += 2 * sizeof(*lp)) if (lp[0] == p[1] && ((lp[1] == ' ' && len == 2) || lp[1] == p[2]) && !--cnt) { adjust1: vp->m_stop.lno = lno; vp->m_stop.cno = 0; goto ret1; } } /* * If moving backward, reached SOF, which is a movement sink. * We already checked for starting there. */ vp->m_stop.lno = 1; vp->m_stop.cno = 0; /* * All commands move to the end of the range. * * !!! * Historic practice is the section cut was in line mode if it started * from column 0 and was in the backward direction. Otherwise, left * motion commands adjust the starting point to the character before * the current one. What makes this worse is that if it cut to line * mode it also went to the first non-. */ ret1: if (vp->m_start.cno == 0) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SETFNB); --vp->m_start.lno; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; vp->m_final = vp->m_stop; return (0); } ================================================ FILE: vi/v_sentence.c ================================================ /* $OpenBSD: v_sentence.c,v 1.8 2022/12/26 19:16:04 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * !!! * In historic vi, a sentence was delimited by a '.', '?' or '!' character * followed by TWO spaces or a newline. One or more empty lines was also * treated as a separate sentence. The Berkeley documentation for historical * vi states that any number of ')', ']', '"' and '\'' characters can be * between the delimiter character and the spaces or end of line, however, * the historical implementation did not handle additional '"' characters. * We follow the documentation here, not the implementation. * * Once again, historical vi didn't do sentence movements associated with * counts consistently, mostly in the presence of lines containing only * white-space characters. * * This implementation also permits a single tab to delimit sentences, and * treats lines containing only white-space characters as empty lines. * Finally, tabs are eaten (along with spaces) when skipping to the start * of the text following a "sentence". */ /* * v_sentencef -- [count]) * Move forward count sentences. * * PUBLIC: int v_sentencef(SCR *, VICMD *); */ int v_sentencef(SCR *sp, VICMD *vp) { enum { BLANK, NONE, PERIOD } state; VCS cs; size_t len; unsigned long cnt; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * If in white-space, the next start of sentence counts as one. * This may not handle " . " correctly, but it's real unclear * what correctly means in that case. */ if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) { if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) { if (vp->m_start.lno != cs.cs_lno || vp->m_start.cno != cs.cs_cno) goto okret; return (1); } } for (state = NONE;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) break; if (cs.cs_flags == CS_EOL) { if ((state == PERIOD || state == BLANK) && --cnt == 0) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && isblank(cs.cs_ch) && cs_fblank(sp, &cs)) return (1); goto okret; } state = NONE; continue; } if (cs.cs_flags == CS_EMP) { /* An EMP is two sentences. */ if (--cnt == 0) goto okret; if (cs_fblank(sp, &cs)) return (1); if (--cnt == 0) goto okret; state = NONE; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': state = PERIOD; break; case ')': case ']': case '"': case '\'': if (state != PERIOD) state = NONE; break; case '\t': if (state == PERIOD) state = BLANK; /* FALLTHROUGH */ case ' ': if (state == PERIOD) { state = BLANK; break; } if (state == BLANK && --cnt == 0) { if (cs_fblank(sp, &cs)) return (1); goto okret; } /* FALLTHROUGH */ default: state = NONE; break; } } /* EOF is a movement sink, but it's an error not to have moved. */ if (vp->m_start.lno == cs.cs_lno && vp->m_start.cno == cs.cs_cno) { v_eof(sp, NULL); return (1); } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * Historic, uh, features, yeah, that's right, call 'em features. * If the starting and ending cursor positions are at the first * column in their lines, i.e. the movement is cutting entire lines, * the buffer is in line mode, and the ending position is the last * character of the previous line. Note check to make sure that * it's not within a single line. * * Non-motion commands move to the end of the range. Delete and * yank stay at the start. Ignore others. Adjust the end of the * range for motion commands. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (vp->m_start.lno < vp->m_stop.lno) { if (db_get(sp, --vp->m_stop.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_stop.cno = len ? len - 1 : 0; } F_SET(vp, VM_LMODE); } else --vp->m_stop.cno; vp->m_final = vp->m_start; } else vp->m_final = vp->m_stop; return (0); } /* * v_sentenceb -- [count]( * Move backward count sentences. * * PUBLIC: int v_sentenceb(SCR *, VICMD *); */ int v_sentenceb(SCR *sp, VICMD *vp) { VCS cs; recno_t slno; size_t len, scno; unsigned long cnt; int last; /* * !!! * Historic vi permitted the user to hit SOF repeatedly. */ if (vp->m_start.lno == 1 && vp->m_start.cno == 0) return (0); cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; /* * !!! * In empty lines, skip to the previous non-white-space character. * If in text, skip to the previous white-space character. Believe * it or not, in the paragraph: * ab cd. * AB CD. * if the cursor is on the 'A' or 'B', ( moves to the 'a'. If it * is on the ' ', 'C' or 'D', it moves to the 'A'. Yes, Virginia, * Berkeley was once a major center of drug activity. */ if (cs.cs_flags == CS_EMP) { if (cs_bblank(sp, &cs)) return (1); for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != CS_EOL) break; } } else if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } for (last = 0;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) /* SOF is a movement sink. */ break; if (cs.cs_flags == CS_EOL) { last = 1; continue; } if (cs.cs_flags == CS_EMP) { if (--cnt == 0) goto ret; if (cs_bblank(sp, &cs)) return (1); last = 0; continue; } switch (cs.cs_ch) { case '.': case '?': case '!': if (!last || --cnt != 0) { last = 0; continue; } ret: slno = cs.cs_lno; scno = cs.cs_cno; /* * Move to the start of the sentence, skipping blanks * and special characters. */ do { if (cs_next(sp, &cs)) return (1); } while (!cs.cs_flags && (cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'')); if ((cs.cs_flags || isblank(cs.cs_ch)) && cs_fblank(sp, &cs)) return (1); /* * If it was ". xyz", with the cursor on the 'x', or * "end. ", with the cursor in the spaces, or the * beginning of a sentence preceded by an empty line, * we can end up where we started. Fix it. */ if (vp->m_start.lno != cs.cs_lno || vp->m_start.cno > cs.cs_cno) goto okret; /* * Well, if an empty line preceded possible blanks * and the sentence, it could be a real sentence. */ for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_EOL) continue; if (cs.cs_flags == 0 && isblank(cs.cs_ch)) continue; break; } if (cs.cs_flags == CS_EMP) goto okret; /* But it wasn't; try again. */ ++cnt; cs.cs_lno = slno; cs.cs_cno = scno; last = 0; break; case '\t': last = 1; break; default: last = cs.cs_flags == CS_EOL || isblank(cs.cs_ch) || cs.cs_ch == ')' || cs.cs_ch == ']' || cs.cs_ch == '"' || cs.cs_ch == '\'' ? 1 : 0; } } okret: vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * !!! * If the starting and stopping cursor positions are at the first * columns in the line, i.e. the movement is cutting an entire line, * the buffer is in line mode, and the starting position is the last * character of the previous line. * * All commands move to the end of the range. Adjust the start of * the range for motion commands. */ if (ISMOTION(vp)) { if (vp->m_start.cno == 0 && (cs.cs_flags != 0 || vp->m_stop.cno == 0)) { if (db_get(sp, --vp->m_start.lno, DBG_FATAL, NULL, &len)) return (1); vp->m_start.cno = len ? len - 1 : 0; F_SET(vp, VM_LMODE); } else --vp->m_start.cno; } vp->m_final = vp->m_stop; return (0); } ================================================ FILE: vi/v_status.c ================================================ /* $OpenBSD: v_status.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_status -- ^G * Show the file status. * * PUBLIC: int v_status(SCR *, VICMD *); */ int v_status(SCR *sp, VICMD *vp) { (void)msgq_status(sp, vp->m_start.lno, MSTAT_SHOWLAST); return (0); } ================================================ FILE: vi/v_txt.c ================================================ /* $OpenBSD: v_txt.c,v 1.36 2022/12/26 19:16:01 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) static int txt_abbrev(SCR *, TEXT *, CHAR_T *, int, int *, int *); static void txt_ai_resolve(SCR *, TEXT *, int *); static TEXT *txt_backup(SCR *, TEXTH *, TEXT *, u_int32_t *); static int txt_dent(SCR *, TEXT *, int, int); static int txt_emark(SCR *, TEXT *, size_t); static void txt_err(SCR *, TEXTH *); static int txt_fc(SCR *, TEXT *, int *); static int txt_fc_col(SCR *, int, ARGS **); static int txt_hex(SCR *, TEXT *); static int txt_insch(SCR *, TEXT *, CHAR_T *, unsigned int); static int txt_isrch(SCR *, VICMD *, TEXT *, u_int8_t *); static int txt_map_end(SCR *); static int txt_map_init(SCR *); static int txt_margin(SCR *, TEXT *, TEXT *, int *, u_int32_t); static void txt_nomorech(SCR *); static void txt_Rresolve(SCR *, TEXTH *, TEXT *, const size_t); static int txt_resolve(SCR *, TEXTH *, u_int32_t); static int txt_showmatch(SCR *, TEXT *); static void txt_unmap(SCR *, TEXT *, u_int32_t *); /* Cursor character (space is hard to track on the screen). */ #if defined(DEBUG) && 0 # undef CH_CURSOR # define CH_CURSOR '+' #endif /* if defined(DEBUG) && 0 */ /* * v_tcmd -- * Fill a buffer from the terminal for vi. * * PUBLIC: int v_tcmd(SCR *, VICMD *, CHAR_T, unsigned int); */ int v_tcmd(SCR *sp, VICMD *vp, CHAR_T prompt, unsigned int flags) { /* Normally, we end up where we started. */ vp->m_final.lno = sp->lno; vp->m_final.cno = sp->cno; /* Initialize the map. */ if (txt_map_init(sp)) return (1); /* Move to the last line. */ sp->lno = TMAP[0].lno; sp->cno = 0; /* Don't update the modeline for now. */ F_SET(sp, SC_TINPUT_INFO); /* Set the input flags. */ LF_SET(TXT_APPENDEOL | TXT_CR | TXT_ESCAPE | TXT_INFOLINE | TXT_MAPINPUT); if (O_ISSET(sp, O_ALTWERASE)) LF_SET(TXT_ALTWERASE); if (O_ISSET(sp, O_TTYWERASE)) LF_SET(TXT_TTYWERASE); /* Do the input thing. */ if (v_txt(sp, vp, NULL, NULL, 0, prompt, 0, 1, flags)) return (1); /* Re-enable the modeline updates. */ F_CLR(sp, SC_TINPUT_INFO); /* Clean up the map. */ if (txt_map_end(sp)) return (1); if (IS_ONELINE(sp)) F_SET(sp, SC_SCR_REDRAW); /* XXX */ /* Set the cursor to the resulting position. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; return (0); } /* * txt_map_init * Initialize the screen map for colon command-line input. */ static int txt_map_init(SCR *sp) { SMAP *esmp; VI_PRIVATE *vip; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* * Fake like the user is doing input on the last line of the * screen. This makes all of the scrolling work correctly, * and allows us the use of the vi text editing routines, not * to mention practically infinite length ex commands. * * Save the current location. */ vip->sv_tm_lno = TMAP->lno; vip->sv_tm_soff = TMAP->soff; vip->sv_tm_coff = TMAP->coff; vip->sv_t_maxrows = sp->t_maxrows; vip->sv_t_minrows = sp->t_minrows; vip->sv_t_rows = sp->t_rows; /* * If it's a small screen, TMAP may be small for the screen. * Fix it, filling in fake lines as we go. */ if (IS_SMALL(sp)) for (esmp = HMAP + (sp->t_maxrows - 1); TMAP < esmp; ++TMAP) { TMAP[1].lno = TMAP[0].lno + 1; TMAP[1].coff = HMAP->coff; TMAP[1].soff = 1; } /* Build the fake entry. */ TMAP[1].lno = TMAP[0].lno + 1; TMAP[1].soff = 1; TMAP[1].coff = 0; SMAP_FLUSH(&TMAP[1]); ++TMAP; /* Reset the screen information. */ sp->t_rows = sp->t_minrows = ++sp->t_maxrows; } return (0); } /* * txt_map_end * Reset the screen map for colon command-line input. */ static int txt_map_end(SCR *sp) { VI_PRIVATE *vip; size_t cnt; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* Restore the screen information. */ sp->t_rows = vip->sv_t_rows; sp->t_minrows = vip->sv_t_minrows; sp->t_maxrows = vip->sv_t_maxrows; /* * If it's a small screen, TMAP may be wrong. Clear any * lines that might have been overwritten. */ if (IS_SMALL(sp)) { for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { (void)sp->gp->scr_move(sp, cnt, 0); (void)sp->gp->scr_clrtoeol(sp); } TMAP = HMAP + (sp->t_rows - 1); } else --TMAP; /* * The map may be wrong if the user entered more than one * (logical) line. Fix it. If the user entered a whole * screen, this will be slow, but we probably don't care. */ if (!O_ISSET(sp, O_LEFTRIGHT)) while (vip->sv_tm_lno != TMAP->lno || vip->sv_tm_soff != TMAP->soff) if (vs_sm_1down(sp)) return (1); } /* * Invalidate the cursor and the line size cache, the line never * really existed. This fixes bugs where the user searches for * the last line on the screen + 1 and the refresh routine thinks * that's where we just were. */ VI_SCR_CFLUSH(vip); F_SET(vip, VIP_CUR_INVALID); return (0); } /* * If doing input mapping on the colon command line, may need to unmap * based on the command. */ #define UNMAP_TST \ FL_ISSET(ec_flags, EC_MAPINPUT) && LF_ISSET(TXT_INFOLINE) /* * Internally, we maintain tp->lno and tp->cno, externally, everyone uses * sp->lno and sp->cno. Make them consistent as necessary. */ #define UPDATE_POSITION(sp, tp) { \ (sp)->lno = (tp)->lno; \ (sp)->cno = (tp)->cno; \ } /* * v_txt -- * Vi text input. * * PUBLIC: int v_txt(SCR *, VICMD *, MARK *, * PUBLIC: const char *, size_t, CHAR_T, recno_t, unsigned long, u_int32_t); */ int v_txt(SCR *sp, VICMD *vp, MARK *tm, const char *lp, size_t len, CHAR_T prompt, recno_t ai_line, unsigned long rcount, u_int32_t flags) { EVENT ev, *evp = NULL; /* Current event. */ EVENT fc; /* File name completion event. */ GS *gp; TEXT *ntp, *tp; /* Input text structures. */ TEXT ait; /* Autoindent text structure. */ TEXT wmt; /* Wrapmargin text structure. */ TEXTH *tiqh; VI_PRIVATE *vip; abb_t abb; /* State of abbreviation checks. */ carat_t carat; /* State of the "[^0]^D" sequences. */ quote_t quote; /* State of quotation. */ size_t owrite, insert; /* Temporary copies of TEXT fields. */ size_t margin; /* Wrapmargin value. */ size_t rcol; /* 0-N: insert offset in the replay buffer. */ size_t tcol; /* Temporary column. */ u_int32_t ec_flags; /* Input mapping flags. */ #define IS_RESTART 0x01 /* Reset the incremental search. */ #define IS_RUNNING 0x02 /* Incremental search turned on. */ u_int8_t is_flags; int abcnt, ab_turnoff; /* Abbreviation character count, switch. */ int filec_redraw; /* Redraw after the file completion routine. */ int hexcnt; /* Hex character count. */ int showmatch; /* Showmatch set on this character. */ int wm_set, wm_skip; /* Wrapmargin happened, blank skip flags. */ int max, tmp; int nochange; char *p; gp = sp->gp; vip = VIP(sp); /* * Set the input flag, so tabs get displayed correctly * and everyone knows that the text buffer is in use. */ F_SET(sp, SC_TINPUT); /* * Get one TEXT structure with some initial buffer space, reusing * the last one if it's big enough. (All TEXT bookkeeping fields * default to 0 -- text_init() handles this.) If changing a line, * copy it into the TEXT buffer. */ tiqh = &sp->tiq; if (!TAILQ_EMPTY(tiqh)) { tp = TAILQ_FIRST(tiqh); if (TAILQ_NEXT(tp, q) || tp->lb_len < len + 32) { text_lfree(tiqh); goto newtp; } tp->ai = tp->insert = tp->offset = tp->owrite = 0; if (lp != NULL) { tp->len = len; memmove(tp->lb, lp, len); } else tp->len = 0; } else { newtp: if ((tp = text_init(sp, lp, len, len + 32)) == NULL) return (1); TAILQ_INSERT_HEAD(tiqh, tp, q); } /* Set default termination condition. */ tp->term = TERM_OK; /* Set the starting line, column. */ tp->lno = sp->lno; tp->cno = sp->cno; /* * Set the insert and overwrite counts. If overwriting characters, * do insertion afterward. If not overwriting characters, assume * doing insertion. If change is to a mark, emphasize it with an * CH_ENDMARK character. */ if (len) { if (LF_ISSET(TXT_OVERWRITE)) { tp->owrite = (tm->cno - tp->cno) + 1; tp->insert = (len - tm->cno) - 1; } else tp->insert = len - tp->cno; if (LF_ISSET(TXT_EMARK) && txt_emark(sp, tp, tm->cno)) return (1); } /* * Many of the special cases in text input are to handle autoindent * support. Somebody decided that it would be a good idea if "^^D" * and "0^D" deleted all of the autoindented characters. In an editor * that takes single character input from the user, this beggars the * imagination. Note also, "^^D" resets the next lines' autoindent, * but "0^D" doesn't. * * We assume that autoindent only happens on empty lines, so insert * and overwrite will be zero. If doing autoindent, figure out how * much indentation we need and fill it in. Update input column and * screen cursor as necessary. */ if (LF_ISSET(TXT_AUTOINDENT) && ai_line != OOBLNO) { if (v_txt_auto(sp, ai_line, NULL, 0, tp)) return (1); tp->cno = tp->ai; } else { /* * The cc and S commands have a special feature -- leading * characters are handled as autoindent characters. * Beauty! */ if (LF_ISSET(TXT_AICHARS)) { tp->offset = 0; tp->ai = tp->cno; } else tp->offset = tp->cno; } /* If getting a command buffer from the user, there may be a prompt. */ if (LF_ISSET(TXT_PROMPT)) { tp->lb[tp->cno++] = prompt; ++tp->len; ++tp->offset; } /* * If appending after the end-of-line, add a space into the buffer * and move the cursor right. This space is inserted, i.e. pushed * along, and then deleted when the line is resolved. Assumes that * the cursor is already positioned at the end of the line. This * avoids the nastiness of having the cursor reside on a magical * column, i.e. a column that doesn't really exist. The only down * side is that we may wrap lines or scroll the screen before it's * strictly necessary. Not a big deal. */ if (LF_ISSET(TXT_APPENDEOL)) { tp->lb[tp->cno] = CH_CURSOR; ++tp->len; ++tp->insert; (void)vs_change(sp, tp->lno, LINE_RESET); } /* * Historic practice is that the wrapmargin value was a distance * from the RIGHT-HAND margin, not the left. It's more useful to * us as a distance from the left-hand margin, i.e. the same as * the wraplen value. The wrapmargin option is historic practice. * Nvi added the wraplen option so that it would be possible to * edit files with consistent margins without knowing the number of * columns in the window. * * XXX * Setting margin causes a significant performance hit. Normally * we don't update the screen if there are keys waiting, but we * have to if margin is set, otherwise the screen routines don't * know where the cursor is. * * !!! * Abbreviated keys were affected by the wrapmargin option in the * historic 4BSD vi. Mapped keys were usually, but sometimes not. * See the comment in vi/v_text():set_txt_std for more information. * * !!! * One more special case. If an inserted character causes * wrapmargin to split the line, the next user entered character is * discarded if it's a character. */ wm_set = wm_skip = 0; if (LF_ISSET(TXT_WRAPMARGIN)) if ((margin = O_VAL(sp, O_WRAPMARGIN)) != 0) margin = sp->cols - margin; else margin = O_VAL(sp, O_WRAPLEN); else margin = 0; /* Initialize abbreviation checks. */ abcnt = ab_turnoff = 0; abb = F_ISSET(gp, G_ABBREV) && LF_ISSET(TXT_MAPINPUT) ? AB_INWORD : AB_NOTSET; /* * Set up the dot command. Dot commands are done by saving the actual * characters and then reevaluating them so that things like wrapmargin * can change between the insert and the replay. * * !!! * Historically, vi did not remap or reabbreviate replayed input. (It * did beep at you if you changed an abbreviation and then replayed the * input. We're not that compatible.) We don't have to do anything to * avoid remapping, as we're not getting characters from the terminal * routines. Turn the abbreviation check off. * * XXX * It would be nice if we could swallow backspaces and such, but it's * not all that easy to do. What we can do is turn off the common * error messages during the replay. Otherwise, when the user enters * an illegal command, e.g., "Iab", * and then does a '.', they get a list of error messages after command * completion. */ rcol = 0; if (LF_ISSET(TXT_REPLAY)) { abb = AB_NOTSET; LF_CLR(TXT_RECORD); } /* Other text input mode setup. */ quote = Q_NOTSET; carat = C_NOTSET; nochange = 0; FL_INIT(is_flags, LF_ISSET(TXT_SEARCHINCR) ? IS_RESTART | IS_RUNNING : 0); filec_redraw = hexcnt = showmatch = 0; /* Initialize input flags. */ ec_flags = LF_ISSET(TXT_MAPINPUT) ? EC_MAPINPUT : 0; /* Refresh the screen. */ UPDATE_POSITION(sp, tp); if (vs_refresh(sp, 1)) return (1); /* If it's dot, just do it now. */ if (F_ISSET(vp, VC_ISDOT)) goto replay; /* Get an event. */ evp = &ev; next: if (v_event_get(sp, evp, 0, ec_flags)) return (1); /* * If file completion overwrote part of the screen and nothing else has * been displayed, clean up. We don't do this as part of the normal * message resolution because we know the user is on the colon command * line and there's no reason to enter explicit characters to continue. */ if (filec_redraw && !F_ISSET(sp, SC_SCR_EXWROTE)) { filec_redraw = 0; fc.e_event = E_REPAINT; fc.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; fc.e_tlno = sp->rows; vip->linecount = vip->lcontinue = vip->totalcount = 0; (void)vs_repaint(sp, &fc); (void)vs_refresh(sp, 1); } /* Deal with all non-character events. */ switch (evp->e_event) { case E_CHARACTER: break; case E_ERR: case E_EOF: F_SET(sp, SC_EXIT_FORCE); return (1); case E_REPAINT: if (vs_repaint(sp, &ev)) return (1); goto next; case E_WRESIZE: /* interrupts the input mode. */ sp->gp->scr_imctrl(sp, IMCTRL_OFF); v_emsg(sp, NULL, VIM_WRESIZE); /* FALLTHROUGH */ default: if (evp->e_event != E_INTERRUPT && evp->e_event != E_WRESIZE) v_event_err(sp, evp); /* * !!! * Historically, exited the user from text input * mode or cancelled a colon command, and returned to command * mode. It also beeped the terminal, but that seemed a bit * excessive. */ /* * If we are recording, morph into key so that * we can repeat the command safely: there is no way to * invalidate the repetition of an instance of a command, * which would be the alternative possibility. * * If we are not recording (most likely on the command line), * simply discard the input and return to command mode * so that an INTERRUPT doesn't become for example a file * completion request. -aymeric */ if (LF_ISSET(TXT_RECORD)) { evp->e_event = E_CHARACTER; evp->e_c = 033; evp->e_flags = 0; evp->e_value = K_ESCAPE; break; } else { tp->term = TERM_ESC; goto k_escape; } } /* * !!! * If the first character of the input is a NULL, replay the previous * input. (Historically, it's okay to replay non-existent input.) * This was not documented as far as I know, and is a great test of vi * clones. */ if (LF_ISSET(TXT_RECORD) && rcol == 0 && evp->e_c == '\0') { if (vip->rep == NULL) goto done; abb = AB_NOTSET; LF_CLR(TXT_RECORD); LF_SET(TXT_REPLAY); goto replay; } /* * File name completion and colon command-line editing. We don't * have enough meta characters, so we expect people to overload * them. If the two characters are the same, then we do file name * completion if the cursor is past the first column, and do colon * command-line editing if it's not. */ if (quote == Q_NOTSET) { int L__cedit, L__filec; L__cedit = L__filec = 0; if (LF_ISSET(TXT_CEDIT) && O_STR(sp, O_CEDIT) != NULL && O_STR(sp, O_CEDIT)[0] == evp->e_c) L__cedit = 1; if (LF_ISSET(TXT_FILEC) && O_STR(sp, O_FILEC) != NULL && O_STR(sp, O_FILEC)[0] == evp->e_c) L__filec = 1; if (L__cedit == 1 && (L__filec == 0 || tp->cno == tp->offset)) { tp->term = TERM_CEDIT; goto k_escape; } if (L__filec == 1) { if (txt_fc(sp, tp, &filec_redraw)) goto err; goto resolve; } } /* Abbreviation overflow check. See comment in txt_abbrev(). */ #define MAX_ABBREVIATION_EXPANSION 256 if (F_ISSET(&evp->e_ch, CH_ABBREVIATED)) { if (++abcnt > MAX_ABBREVIATION_EXPANSION) { if (v_event_flush(sp, CH_ABBREVIATED)) msgq(sp, M_ERR, "Abbreviation exceeded expansion limit: characters discarded"); abcnt = 0; if (LF_ISSET(TXT_REPLAY)) goto done; goto resolve; } } else abcnt = 0; /* Check to see if the character fits into the replay buffers. */ if (LF_ISSET(TXT_RECORD)) { BINC_GOTO(sp, vip->rep, vip->rep_len, (rcol + 1) * sizeof(EVENT)); vip->rep[rcol++] = *evp; } replay: if (LF_ISSET(TXT_REPLAY)) evp = vip->rep + rcol++; /* Wrapmargin check for leading space. */ if (wm_skip) { wm_skip = 0; if (evp->e_c == ' ') goto resolve; } /* If quoted by someone else, simply insert the character. */ if (F_ISSET(&evp->e_ch, CH_QUOTED)) goto insq_ch; /* * !!! * If this character was quoted by a K_VLNEXT, replace the placeholder * (a carat) with the new character. We've already adjusted the cursor * because it has to appear on top of the placeholder character. * Historic practice. * * Skip tests for abbreviations; ":ab xa XA" followed by "ixa^V" * doesn't perform an abbreviation. Special case, ^V^J (not ^V^M) is * the same as ^J, historically. */ if (quote == Q_VTHIS) { FL_CLR(ec_flags, EC_QUOTED); if (LF_ISSET(TXT_MAPINPUT)) FL_SET(ec_flags, EC_MAPINPUT); if (evp->e_value != K_NL) { quote = Q_NOTSET; goto insl_ch; } quote = Q_NOTSET; } /* * !!! * Translate "[isxdigit()]*" to a character with a hex value: * this test delimits the value by any non-hex character. Offset by * one, we use 0 to mean that we've found . */ if (hexcnt > 1 && !isxdigit(evp->e_c)) { hexcnt = 0; if (txt_hex(sp, tp)) goto err; } switch (evp->e_value) { case K_CR: /* Carriage return. */ case K_NL: /* New line. */ /* Return in script windows and the command line. */ k_cr: if (LF_ISSET(TXT_CR)) { /* * If this was a map, we may have not displayed * the line. Display it, just in case. * * If a script window and not the colon line, * push a so it gets executed. */ if (LF_ISSET(TXT_INFOLINE)) { if (vs_change(sp, tp->lno, LINE_RESET)) goto err; } else if (F_ISSET(sp, SC_SCRIPT)) (void)v_event_push(sp, NULL, "\r", 1, CH_NOMAP); /* Set term condition: if empty. */ if (tp->cno <= tp->offset) tp->term = TERM_CR; /* * Set term condition: if searching incrementally and * the user entered a pattern, return a completed * search, regardless if the entire pattern was found. */ if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1) tp->term = TERM_SEARCH; goto k_escape; } #define LINE_RESOLVE { \ /* \ * Handle abbreviations. If there was one, discard the \ * replay characters. \ */ \ if (abb == AB_INWORD && \ !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { \ if (txt_abbrev(sp, tp, &evp->e_c, \ LF_ISSET(TXT_INFOLINE), &tmp, \ &ab_turnoff)) \ goto err; \ if (tmp) { \ if (LF_ISSET(TXT_RECORD)) \ rcol -= tmp + 1; \ goto resolve; \ } \ } \ if (abb != AB_NOTSET) \ abb = AB_NOTWORD; \ if (UNMAP_TST) \ txt_unmap(sp, tp, &ec_flags); \ /* \ * Delete any appended cursor. It's possible to get in \ * situations where TXT_APPENDEOL is set but tp->insert \ * is 0 when using the R command and all the characters \ * are tp->owrite characters. \ */ \ if (LF_ISSET(TXT_APPENDEOL) && tp->insert > 0) { \ --tp->len; \ --tp->insert; \ } \ } LINE_RESOLVE; /* * Save the current line information for restoration in * txt_backup(), and set the line final length. */ tp->sv_len = tp->len; tp->sv_cno = tp->cno; tp->len = tp->cno; /* Update the old line. */ if (vs_change(sp, tp->lno, LINE_RESET)) goto err; /* * Historic practice, when the autoindent edit option was set, * was to delete characters following the inserted * newline. This affected the 'R', 'c', and 's' commands; 'c' * and 's' retained the insert characters only, 'R' moved the * overwrite and insert characters into the next TEXT structure. * We keep track of the number of characters erased for the 'R' * command so that the final resolution of the line is correct. */ tp->R_erase = 0; owrite = tp->owrite; insert = tp->insert; if (LF_ISSET(TXT_REPLACE) && owrite != 0) { for (p = tp->lb + tp->cno; owrite > 0 && isblank(*p); ++p, --owrite, ++tp->R_erase); if (owrite == 0) for (; insert > 0 && isblank(*p); ++p, ++tp->R_erase, --insert); } else { p = tp->lb + tp->cno + owrite; if (O_ISSET(sp, O_AUTOINDENT)) for (; insert > 0 && isblank(*p); ++p, --insert); owrite = 0; } /* * !!! * Create a new line and insert the new TEXT into the queue. * DON'T insert until the old line has been updated, or the * inserted line count in line.c:db_get() will be wrong. */ if ((ntp = text_init(sp, p, insert + owrite, insert + owrite + 32)) == NULL) goto err; TAILQ_INSERT_TAIL(&sp->tiq, ntp, q); /* Set up bookkeeping for the new line. */ ntp->insert = insert; ntp->owrite = owrite; ntp->lno = tp->lno + 1; /* * Reset the autoindent line value. 0^D keeps the autoindent * line from changing, ^D changes the level, even if there were * no characters in the old line. Note, if using the current * tp structure, use the cursor as the length, the autoindent * characters may have been erased. */ if (LF_ISSET(TXT_AUTOINDENT)) { if (nochange) { nochange = 0; if (v_txt_auto(sp, OOBLNO, &ait, ait.ai, ntp)) goto err; FREE_SPACE(sp, ait.lb, ait.lb_len); } else if (v_txt_auto(sp, OOBLNO, tp, tp->cno, ntp)) goto err; carat = C_NOTSET; } /* Reset the cursor. */ ntp->cno = ntp->ai; /* * If we're here because wrapmargin was set and we've broken a * line, there may be additional information (i.e. the start of * a line) in the wmt structure. */ if (wm_set) { if (wmt.offset != 0 || wmt.owrite != 0 || wmt.insert != 0) { #define WMTSPACE wmt.offset + wmt.owrite + wmt.insert BINC_GOTO(sp, ntp->lb, ntp->lb_len, ntp->len + WMTSPACE + 32); memmove(ntp->lb + ntp->cno, wmt.lb, WMTSPACE); ntp->len += WMTSPACE; ntp->cno += wmt.offset; ntp->owrite = wmt.owrite; ntp->insert = wmt.insert; } wm_set = 0; } /* New lines are TXT_APPENDEOL. */ if (ntp->owrite == 0 && ntp->insert == 0) { BINC_GOTO(sp, ntp->lb, ntp->lb_len, ntp->len + 1); LF_SET(TXT_APPENDEOL); ntp->lb[ntp->cno] = CH_CURSOR; ++ntp->insert; ++ntp->len; } /* Swap old and new TEXT's, and update the new line. */ tp = ntp; if (vs_change(sp, tp->lno, LINE_INSERT)) goto err; goto resolve; case K_ESCAPE: /* Escape. */ if (!LF_ISSET(TXT_ESCAPE)) goto ins_ch; /* If we have a count, start replaying the input. */ if (rcount > 1) { --rcount; rcol = 0; abb = AB_NOTSET; LF_CLR(TXT_RECORD); LF_SET(TXT_REPLAY); /* * Some commands (e.g. 'o') need a for each * repetition. */ if (LF_ISSET(TXT_ADDNEWLINE)) goto k_cr; /* * The R command turns into the 'a' command after the * first repetition. */ if (LF_ISSET(TXT_REPLACE)) { tp->insert = tp->owrite; tp->owrite = 0; LF_CLR(TXT_REPLACE); } goto replay; } /* Set term condition: if empty. */ if (tp->cno <= tp->offset) tp->term = TERM_ESC; /* * Set term condition: if searching incrementally and the user * entered a pattern, return a completed search, regardless if * the entire pattern was found. */ if (FL_ISSET(is_flags, IS_RUNNING) && tp->cno >= tp->offset + 1) tp->term = TERM_SEARCH; k_escape: LINE_RESOLVE; /* * Clean up for the 'R' command, restoring overwrite * characters, and making them into insert characters. */ if (LF_ISSET(TXT_REPLACE)) txt_Rresolve(sp, &sp->tiq, tp, len); /* * If there are any overwrite characters, copy down * any insert characters, and decrement the length. */ if (tp->owrite) { if (tp->insert) memmove(tp->lb + tp->cno, tp->lb + tp->cno + tp->owrite, tp->insert); tp->len -= tp->owrite; } /* * Optionally resolve the lines into the file. If not * resolving the lines into the file, end the line with * a nul. If the line is empty, then set the length to * 0, the termination condition has already been set. * * XXX * This is wrong, should pass back a length. */ if (LF_ISSET(TXT_RESOLVE)) { if (txt_resolve(sp, &sp->tiq, flags)) goto err; } else { BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); tp->lb[tp->len] = '\0'; } /* * Set the return cursor position to rest on the last * inserted character. */ if (tp->cno != 0) --tp->cno; /* Update the last line. */ if (vs_change(sp, tp->lno, LINE_RESET)) return (1); goto done; case K_CARAT: /* Delete autoindent chars. */ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat = C_CARATSET; goto ins_ch; case K_ZERO: /* Delete autoindent chars. */ if (tp->cno <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) carat = C_ZEROSET; goto ins_ch; case K_CNTRLD: /* Delete autoindent char. */ /* * If in the first column or no characters to erase, ignore * the ^D (this matches historic practice). If not doing * autoindent or already inserted non-ai characters, it's a * literal. The latter test is done in the switch, as the * CARAT forms are N + 1, not N. */ if (!LF_ISSET(TXT_AUTOINDENT)) goto ins_ch; if (tp->cno == 0) goto resolve; switch (carat) { case C_CARATSET: /* ^^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) goto ins_ch; /* Save the ai string for later. */ ait.lb = NULL; ait.lb_len = 0; BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai); memmove(ait.lb, tp->lb, tp->ai); ait.ai = ait.len = tp->ai; carat = C_NOTSET; nochange = 0; goto leftmargin; case C_ZEROSET: /* 0^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset + 1) goto ins_ch; carat = C_NOTSET; leftmargin: tp->lb[tp->cno - 1] = ' '; tp->owrite += tp->cno - tp->offset; tp->ai = 0; tp->cno = tp->offset; break; case C_NOTSET: /* ^D */ if (tp->ai == 0 || tp->cno > tp->ai + tp->offset) goto ins_ch; (void)txt_dent(sp, tp, O_SHIFTWIDTH, 0); break; default: abort(); } break; case K_VERASE: /* Erase the last character. */ /* If can erase over the prompt, return. */ if (tp->cno <= tp->offset && LF_ISSET(TXT_BS)) { tp->term = TERM_BS; goto done; } /* * If at the beginning of the line, try and drop back to a * previously inserted line. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; break; } /* If nothing to erase, bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* Drop back one character. */ --tp->cno; /* * Historically, vi didn't replace the erased characters with * s, presumably because it's easier to fix a minor * typing mistake and continue on if the previous letters are * already there. This is a problem for incremental searching, * because the user can no longer tell where they are in the * colon command line because the cursor is at the last search * point in the screen. So, if incrementally searching, erase * the erased characters from the screen. */ if (FL_ISSET(is_flags, IS_RUNNING) || O_ISSET(sp, O_BSERASE)) tp->lb[tp->cno] = ' '; /* * Increment overwrite, decrement ai if deleted. * * !!! * Historic vi did not permit users to use erase characters * to delete autoindent characters. We do. Eat hot death, * POSIX. */ ++tp->owrite; if (tp->cno < tp->ai) --tp->ai; /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_VWERASE: /* Skip back one word. */ /* * If at the beginning of the line, try and drop back to a * previously inserted line. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; } /* * If at offset, nothing to erase so bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* * The first werase goes back to any autoindent column and the * second werase goes back to the offset. * * !!! * Historic vi did not permit users to use erase characters to * delete autoindent characters. */ if (tp->ai && tp->cno > tp->ai) max = tp->ai; else { tp->ai = 0; max = tp->offset; } /* Skip over trailing space characters. */ while (tp->cno > max && isblank(tp->lb[tp->cno - 1])) { --tp->cno; ++tp->owrite; } if (tp->cno == max) break; /* * There are three types of word erase found on UNIX systems. * They can be identified by how the string /a/b/c is treated * -- as 1, 3, or 6 words. Historic vi had two classes of * characters, and strings were delimited by them and * 's, so, 6 words. The historic tty interface used * 's to delimit strings, so, 1 word. The algorithm * offered in the 4.4BSD tty interface (as stty altwerase) * treats it as 3 words -- there are two classes of * characters, and strings are delimited by them and * 's. The difference is that the type of the first * erased character erased is ignored, which is exactly right * when erasing pathname components. The edit options * TXT_ALTWERASE and TXT_TTYWERASE specify the 4.4BSD tty * interface and the historic tty driver behavior, * respectively, and the default is the same as the historic * vi behavior. * * Overwrite erased characters if doing incremental search; * see comment above. */ if (LF_ISSET(TXT_TTYWERASE)) while (tp->cno > max) { if (isblank(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } else { if (LF_ISSET(TXT_ALTWERASE)) { --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } if (tp->cno > max) tmp = inword(tp->lb[tp->cno - 1]); while (tp->cno > max) { if (tmp != inword(tp->lb[tp->cno - 1]) || isblank(tp->lb[tp->cno - 1])) break; --tp->cno; ++tp->owrite; if (FL_ISSET(is_flags, IS_RUNNING)) tp->lb[tp->cno] = ' '; } } /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_VKILL: /* Restart this line. */ /* * !!! * If at the beginning of the line, try and drop back to a * previously inserted line. Historic vi did not permit * users to go back to previous lines. */ if (tp->cno == 0) { if ((ntp = txt_backup(sp, &sp->tiq, tp, &flags)) == NULL) goto err; tp = ntp; } /* If at offset, nothing to erase so bell the user. */ if (tp->cno <= tp->offset) { if (!LF_ISSET(TXT_REPLAY)) txt_nomorech(sp); break; } /* * First kill goes back to any autoindent and second kill goes * back to the offset. * * !!! * Historic vi did not permit users to use erase characters to * delete autoindent characters. */ if (tp->ai && tp->cno > tp->ai) max = tp->ai; else { tp->ai = 0; max = tp->offset; } tp->owrite += tp->cno - max; /* * Overwrite erased characters if doing incremental search; * see comment above. */ if (FL_ISSET(is_flags, IS_RUNNING)) do { tp->lb[--tp->cno] = ' '; } while (tp->cno > max); else tp->cno = max; /* Reset if we deleted an incremental search character. */ if (FL_ISSET(is_flags, IS_RUNNING)) FL_SET(is_flags, IS_RESTART); break; case K_CNTRLT: /* Add autoindent characters. */ if (!LF_ISSET(TXT_CNTRLT)) goto ins_ch; if (txt_dent(sp, tp, O_SHIFTWIDTH, 1)) goto err; goto ebuf_chk; case K_RIGHTBRACE: case K_RIGHTPAREN: if (LF_ISSET(TXT_SHOWMATCH)) showmatch = 1; goto ins_ch; case K_VLNEXT: /* Quote next character. */ evp->e_c = '^'; quote = Q_VNEXT; /* * Turn on the quote flag so that the underlying routines * quote the next character where it's possible. Turn off * the input mapbiting flag so that we don't remap the next * character. */ FL_SET(ec_flags, EC_QUOTED); FL_CLR(ec_flags, EC_MAPINPUT); /* * !!! * Skip the tests for abbreviations, so ":ab xa XA", * "ixa^V" doesn't perform the abbreviation. */ goto insl_ch; case K_HEXCHAR: hexcnt = 1; goto insq_ch; case K_TAB: if (sp->showmode != SM_COMMAND && quote != Q_VTHIS && O_ISSET(sp, O_EXPANDTAB)) { if (txt_dent(sp, tp, O_TABSTOP, 1)) goto err; goto ebuf_chk; } goto insq_ch; default: /* Insert the character. */ ins_ch: /* * Historically, vi eliminated nul's out of hand. If the * beautify option was set, it also deleted any unknown * ASCII value less than space (040) and the del character * (0177), except for tabs. Unknown is a key word here. * Most vi documentation claims that it deleted everything * but , and , as that's what the original * 4BSD documentation said. This is obviously wrong, * however, as would be included in that list. What * we do is eliminate any unquoted, iscntrl() character that * wasn't a replay and wasn't handled specially, except * or . */ if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(evp->e_c) && evp->e_value != K_FORMFEED && evp->e_value != K_TAB) { msgq(sp, M_BERR, "Illegal character; quote to enter"); if (LF_ISSET(TXT_REPLAY)) goto done; break; } insq_ch: /* * If entering a non-word character after a word, check for * abbreviations. If there was one, discard replay characters. * If entering a blank character, check for unmap commands, * as well. */ if (!inword(evp->e_c)) { if (abb == AB_INWORD && !LF_ISSET(TXT_REPLAY) && F_ISSET(gp, G_ABBREV)) { if (txt_abbrev(sp, tp, &evp->e_c, LF_ISSET(TXT_INFOLINE), &tmp, &ab_turnoff)) goto err; if (tmp) { if (LF_ISSET(TXT_RECORD)) rcol -= tmp + 1; goto resolve; } } if (isblank(evp->e_c) && UNMAP_TST) txt_unmap(sp, tp, &ec_flags); } if (abb != AB_NOTSET) abb = inword(evp->e_c) ? AB_INWORD : AB_NOTWORD; insl_ch: if (txt_insch(sp, tp, &evp->e_c, flags)) goto err; /* * If we're using K_VLNEXT to quote the next character, then * we want the cursor to position itself on the ^ placeholder * we're displaying, to match historic practice. */ if (quote == Q_VNEXT) { --tp->cno; ++tp->owrite; } /* * !!! * Translate "[isxdigit()]*" to a character with * a hex value: this test delimits the value by the max * number of hex bytes. Offset by one, we use 0 to mean * that we've found . */ if (hexcnt != 0 && hexcnt++ == sizeof(CHAR_T) * 2 + 1) { hexcnt = 0; if (txt_hex(sp, tp)) goto err; } /* * Check to see if we've crossed the margin. * * !!! * In the historic vi, the wrapmargin value was figured out * using the display widths of the characters, i.e. * characters were counted as two characters if the list edit * option is set, but as the tabstop edit option number of * characters otherwise. That's what the vs_column() function * gives us, so we use it. */ if (margin != 0) { if (vs_column(sp, &tcol)) goto err; if (tcol >= margin) { if (txt_margin(sp, tp, &wmt, &tmp, flags)) goto err; if (tmp) { if (isblank(evp->e_c)) wm_skip = 1; wm_set = 1; goto k_cr; } } } /* * If we've reached the end of the buffer, then we need to * switch into insert mode. This happens when there's a * change to a mark and the user puts in more characters than * the length of the motion. */ ebuf_chk: if (tp->cno >= tp->len) { BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); LF_SET(TXT_APPENDEOL); tp->lb[tp->cno] = CH_CURSOR; ++tp->insert; ++tp->len; } /* Step the quote state forward. */ if (quote != Q_NOTSET) { if (quote == Q_VNEXT) quote = Q_VTHIS; } break; } #ifdef DEBUG if (tp->cno + tp->insert + tp->owrite != tp->len) { msgq(sp, M_ERR, "len %u != cno: %u ai: %u insert %u overwrite %u", tp->len, tp->cno, tp->ai, tp->insert, tp->owrite); if (LF_ISSET(TXT_REPLAY)) goto done; tp->len = tp->cno + tp->insert + tp->owrite; } #endif /* ifdef DEBUG */ resolve:/* * 1: If we don't need to know where the cursor really is and we're * replaying text, keep going. */ if (margin == 0 && LF_ISSET(TXT_REPLAY)) goto replay; /* * 2: Reset the line. Don't bother unless we're about to wait on * a character or we need to know where the cursor really is. * We have to do this before showing matching characters so the * user can see what they're matching. */ if ((margin != 0 || !KEYS_WAITING(sp)) && vs_change(sp, tp->lno, LINE_RESET)) return (1); /* * 3: If there aren't keys waiting, display the matching character. * We have to do this before resolving any messages, otherwise * the error message from a missing match won't appear correctly. */ if (showmatch) { if (!KEYS_WAITING(sp) && txt_showmatch(sp, tp)) return (1); showmatch = 0; } /* * 4: If there have been messages and we're not editing on the colon * command line or doing file name completion, resolve them. */ if ((vip->totalcount != 0 || F_ISSET(gp, G_BELLSCHED)) && !F_ISSET(sp, SC_TINPUT_INFO) && !filec_redraw && vs_resolve(sp, NULL, 0)) return (1); /* * 5: Refresh the screen if we're about to wait on a character or we * need to know where the cursor really is. */ if (margin != 0 || !KEYS_WAITING(sp)) { UPDATE_POSITION(sp, tp); if (vs_refresh(sp, margin != 0)) return (1); } /* 6: Proceed with the incremental search. */ if (FL_ISSET(is_flags, IS_RUNNING) && txt_isrch(sp, vp, tp, &is_flags)) return (1); /* 7: Next character... */ if (LF_ISSET(TXT_REPLAY)) goto replay; goto next; done: /* Leave input mode. */ F_CLR(sp, SC_TINPUT); /* If recording for playback, save it. */ if (LF_ISSET(TXT_RECORD)) vip->rep_cnt = rcol; /* * If not working on the colon command line, set the final cursor * position. */ if (!F_ISSET(sp, SC_TINPUT_INFO)) { vp->m_final.lno = tp->lno; vp->m_final.cno = tp->cno; } return (0); err: alloc_err: F_CLR(sp, SC_TINPUT); txt_err(sp, &sp->tiq); return (1); } /* * txt_abbrev -- * Handle abbreviations. */ static int txt_abbrev(SCR *sp, TEXT *tp, CHAR_T *pushcp, int isinfoline, int *didsubp, int *turnoffp) { CHAR_T ch, *p; SEQ *qp; size_t len, off; /* Check to make sure we're not at the start of an append. */ *didsubp = 0; if (tp->cno == tp->offset) return (0); /* * Find the start of the "word". * * !!! * We match historic practice, which, as far as I can tell, had an * off-by-one error. The way this worked was that when the inserted * text switched from a "word" character to a non-word character, * vi would check for possible abbreviations. It would then take the * type (i.e. word/non-word) of the character entered TWO characters * ago, and move backward in the text until reaching a character that * was not that type, or the beginning of the insert, the line, or * the file. For example, in the string "abc", when the * character triggered the abbreviation check, the type of the 'b' * character was used for moving through the string. Maybe there's a * reason for not using the first (i.e. 'c') character, but I can't * think of one. * * Terminate at the beginning of the insert or the character after the * offset character -- both can be tested for using tp->offset. */ off = tp->cno - 1; /* Previous character. */ p = tp->lb + off; len = 1; /* One character test. */ if (off == tp->offset || isblank(p[-1])) goto search; if (inword(p[-1])) /* Move backward to change. */ for (;;) { --off; --p; ++len; if (off == tp->offset || !inword(p[-1])) break; } else for (;;) { --off; --p; ++len; if (off == tp->offset || inword(p[-1]) || isblank(p[-1])) break; } /* * !!! * Historic vi exploded abbreviations on the command line. This has * obvious problems in that unabbreviating the string can be extremely * tricky, particularly if the string has, say, an embedded escape * character. Personally, I think it's a stunningly bad idea. Other * examples of problems this caused in historic vi are: * :ab foo bar * :ab foo baz * results in "bar" being abbreviated to "baz", which wasn't what the * user had in mind at all. Also, the commands: * :ab foo bar * :unab foo * resulted in an error message that "bar" wasn't mapped. Finally, * since the string was already exploded by the time the unabbreviate * command got it, all it knew was that an abbreviation had occurred. * Cleverly, it checked the replacement string for its unabbreviation * match, which meant that the commands: * :ab foo1 bar * :ab foo2 bar * :unab foo2 * unabbreviate "foo1", and the commands: * :ab foo bar * :ab bar baz * unabbreviate "foo"! * * Anyway, people neglected to first ask my opinion before they wrote * macros that depend on this stuff, so, we make this work as follows: * * When checking for an abbreviation on the command line, if we get a * string which is terminated and which starts at the beginning * of the line, we check to see it is the abbreviate or unabbreviate * commands. If it is, turn abbreviations off and return as if no * abbreviation was found. Note also, minor trickiness, so that if * the user erases the line and starts another command, we turn the * abbreviations back on. * * This makes the layering look like a Nachos Supreme. */ search: if (isinfoline) { if (off == tp->ai || off == tp->offset) if (ex_is_abbrev(p, len)) { *turnoffp = 1; return (0); } else *turnoffp = 0; else if (*turnoffp) return (0); } /* Check for any abbreviations. */ if ((qp = seq_find(sp, NULL, NULL, p, len, SEQ_ABBREV, NULL)) == NULL) return (0); /* * Push the abbreviation onto the tty stack. Historically, characters * resulting from an abbreviation expansion were themselves subject to * map expansions, O_SHOWMATCH matching etc. This means the expanded * characters will be re-tested for abbreviations. It's difficult to * know what historic practice in this case was, since abbreviations * were applied to :colon command lines, so entering abbreviations that * looped was tricky, although possible. In addition, obvious loops * didn't work as expected. (The command ':ab a b|ab b c|ab c a' will * silently only implement and/or display the last abbreviation.) * * This implementation doesn't recover well from such abbreviations. * The main input loop counts abbreviated characters, and, when it * reaches a limit, discards any abbreviated characters on the queue. * It's difficult to back up to the original position, as the replay * queue would have to be adjusted, and the line state when an initial * abbreviated character was received would have to be saved. */ ch = *pushcp; if (v_event_push(sp, NULL, &ch, 1, CH_ABBREVIATED)) return (1); if (v_event_push(sp, NULL, qp->output, qp->olen, CH_ABBREVIATED)) return (1); /* * If the size of the abbreviation is larger than or equal to the size * of the original text, move to the start of the replaced characters, * and add their length to the overwrite count. * * If the abbreviation is smaller than the original text, we have to * delete the additional overwrite characters and copy down any insert * characters. */ tp->cno -= len; if (qp->olen >= len) tp->owrite += len; else { if (tp->insert) memmove(tp->lb + tp->cno + qp->olen, tp->lb + tp->cno + tp->owrite + len, tp->insert); tp->owrite += qp->olen; tp->len -= len - qp->olen; } /* * We return the length of the abbreviated characters. This is so * the calling routine can replace the replay characters with the * abbreviation. This means that subsequent '.' commands will produce * the same text, regardless of intervening :[un]abbreviate commands. * This is historic practice. */ *didsubp = len; return (0); } /* * txt_unmap -- * Handle the unmap command. */ static void txt_unmap(SCR *sp, TEXT *tp, u_int32_t *ec_flagsp) { size_t len, off; char *p; /* Find the beginning of this "word". */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off) { if (isblank(*p)) { ++p; break; } ++len; if (off == tp->ai || off == tp->offset) break; } /* * !!! * Historic vi exploded input mappings on the command line. See the * txt_abbrev() routine for an explanation of the problems inherent * in this. * * We make this work as follows: If we get a string which is * terminated and which starts at the beginning of the line, we check * to see it is the unmap command. If it is, we return that the input * mapping should be turned off. Note also, minor trickiness, so that * if the user erases the line and starts another command, we go ahead * an turn mapping back on. */ if ((off == tp->ai || off == tp->offset) && ex_is_unmap(p, len)) FL_CLR(*ec_flagsp, EC_MAPINPUT); else FL_SET(*ec_flagsp, EC_MAPINPUT); } /* * txt_ai_resolve -- * When a line is resolved by , review autoindent characters. */ static void txt_ai_resolve(SCR *sp, TEXT *tp, int *changedp) { unsigned long ts; int del; size_t cno, len, new, old, scno, spaces, tab_after_sp, tabs; char *p; *changedp = 0; /* * If the line is empty, has an offset, or no autoindent * characters, we're done. */ if (!tp->len || tp->offset || !tp->ai) return; /* * If the length is less than or equal to the autoindent * characters, delete them. */ if (tp->len <= tp->ai) { tp->ai = tp->cno = tp->len = 0; return; } /* * The autoindent characters plus any leading characters * in the line are resolved into the minimum number of characters. * Historic practice. */ ts = O_VAL(sp, O_TABSTOP); /* Figure out the last screen column. */ for (p = tp->lb, scno = 0, len = tp->len, spaces = tab_after_sp = 0; len-- && isblank(*p); ++p) if (*p == '\t') { if (spaces) tab_after_sp = 1; scno += COL_OFF(scno, ts); } else { ++spaces; ++scno; } /* * If there are no spaces, or no tabs after spaces and less than * ts spaces, it's already minimal. * Keep analysing if expandtab is set. */ if ((!spaces || (!tab_after_sp && spaces < ts)) && !O_ISSET(sp, O_EXPANDTAB)) return; /* Count up spaces/tabs needed to get to the target. */ cno = 0; tabs = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; cno + COL_OFF(cno, ts) <= scno; ++tabs) cno += COL_OFF(cno, ts); } spaces = scno - cno; /* * Figure out how many characters we're dropping -- if we're not * dropping any, it's already minimal, we're done. */ old = p - tp->lb; new = spaces + tabs; if (old == new) return; /* Shift the rest of the characters down, adjust the counts. */ del = old - new; memmove(p - del, p, tp->len - old); tp->len -= del; tp->cno -= del; /* Fill in space/tab characters. */ for (p = tp->lb; tabs--;) *p++ = '\t'; while (spaces--) *p++ = ' '; *changedp = 1; } /* * v_txt_auto -- * Handle autoindent. If aitp isn't NULL, use it, otherwise, * retrieve the line. * * PUBLIC: int v_txt_auto(SCR *, recno_t, TEXT *, size_t, TEXT *); */ int v_txt_auto(SCR *sp, recno_t lno, TEXT *aitp, size_t len, TEXT *tp) { size_t nlen; char *p, *t; if (aitp == NULL) { /* * If the ex append command is executed with an address of 0, * it's possible to get here with a line number of 0. Return * an indent of 0. */ if (lno == 0) { tp->ai = 0; return (0); } if (db_get(sp, lno, DBG_FATAL, &t, &len)) return (1); } else t = aitp->lb; /* Count whitespace characters. */ for (p = t; len > 0; ++p, --len) if (!isblank(*p)) break; /* Set count, check for no indentation. */ if ((nlen = (p - t)) == 0) return (0); /* Make sure the buffer's big enough. */ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen); /* Copy the buffer's current contents up. */ if (tp->len != 0) memmove(tp->lb + nlen, tp->lb, tp->len); tp->len += nlen; /* Copy the indentation into the new buffer. */ memmove(tp->lb, t, nlen); /* Set the autoindent count. */ tp->ai = nlen; return (0); } /* * txt_backup -- * Back up to the previously edited line. */ static TEXT * txt_backup(SCR *sp, TEXTH *tiqh, TEXT *tp, u_int32_t *flagsp) { TEXT *ntp; /* Get a handle on the previous TEXT structure. */ if ((ntp = TAILQ_PREV(tp, _texth, q)) == NULL) { if (!FL_ISSET(*flagsp, TXT_REPLAY)) msgq(sp, M_BERR, "Already at the beginning of the insert"); return (tp); } /* Bookkeeping. */ ntp->len = ntp->sv_len; /* Handle appending to the line. */ if (ntp->owrite == 0 && ntp->insert == 0) { ntp->lb[ntp->len] = CH_CURSOR; ++ntp->insert; ++ntp->len; FL_SET(*flagsp, TXT_APPENDEOL); } else FL_CLR(*flagsp, TXT_APPENDEOL); /* Release the current TEXT. */ TAILQ_REMOVE(tiqh, tp, q); text_free(tp); /* Update the old line on the screen. */ if (vs_change(sp, ntp->lno + 1, LINE_DELETE)) return (NULL); /* Return the new/current TEXT. */ return (ntp); } /* * Text indentation is truly strange. ^T and ^D do movements to the next or * previous shiftwidth value, i.e. for a 1-based numbering, with shiftwidth=3, * ^T moves a cursor on the 7th, 8th or 9th column to the 10th column, and ^D * moves it back. * * !!! * The ^T and ^D characters in historical vi had special meaning only when they * were the first characters entered after entering text input mode. As normal * erase characters couldn't erase autoindent characters (^T in this case), it * meant that inserting text into previously existing text was strange -- ^T * only worked if it was the first keystroke(s), and then could only be erased * using ^D. This implementation treats ^T specially anywhere it occurs in the * input, and permits the standard erase characters to erase the characters it * inserts. * * !!! * A fun test is to try: * :se sw=4 ai list * i^Tx^Tx^Tx^Dx^Dx^Dx * Historic vi loses some of the '$' marks on the line ends, but otherwise gets * it right. * * XXX * Technically, txt_dent should be part of the screen interface, as it requires * knowledge of character sizes, including s, on the screen. It's here * because it's a complicated little beast, and I didn't want to shove it down * into the screen. It's probable that KEY_LEN will call into the screen once * there are screens with different character representations. * * txt_dent -- * Handle ^T indents, ^D outdents. * * If anything changes here, check the ex version to see if it needs similar * changes. */ static int txt_dent(SCR *sp, TEXT *tp, int swopt, int isindent) { CHAR_T ch; unsigned long sw, ts; size_t cno, current, spaces, target, tabs; int ai_reset; ts = O_VAL(sp, O_TABSTOP); sw = O_VAL(sp, swopt); /* * Since we don't know what precedes the character(s) being inserted * (or deleted), the preceding whitespace characters must be resolved. * An example is a , which doesn't need a full shiftwidth number * of columns because it's preceded by s. This is easy to get * if the user sets shiftwidth to a value less than tabstop (or worse, * something for which tabstop isn't a multiple) and then uses ^T to * indent, and ^D to outdent. * * Figure out the current and target screen columns. In the historic * vi, the autoindent column was NOT determined using display widths * of characters as was the wrapmargin column. For that reason, we * can't use the vs_column() function, but have to calculate it here. * This is slow, but it's normally only on the first few characters of * a line. */ for (current = cno = 0; cno < tp->cno; ++cno) current += tp->lb[cno] == '\t' ? COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]); target = current; if (isindent) target += COL_OFF(target, sw); else { --target; target -= target % sw; } /* * The AI characters will be turned into overwrite characters if the * cursor immediately follows them. We test both the cursor position * and the indent flag because there's no single test. (^T can only * be detected by the cursor position, and while we know that the test * is always true for ^D, the cursor can be in more than one place, as * "0^D" and "^D" are different.) */ ai_reset = !isindent || tp->cno == tp->ai + tp->offset; /* * Back up over any previous characters, changing them into * overwrite characters (including any ai characters). Then figure * out the current screen column. */ for (; tp->cno > tp->offset && (tp->lb[tp->cno - 1] == ' ' || tp->lb[tp->cno - 1] == '\t'); --tp->cno, ++tp->owrite); for (current = cno = 0; cno < tp->cno; ++cno) current += tp->lb[cno] == '\t' ? COL_OFF(current, ts) : KEY_LEN(sp, tp->lb[cno]); /* * If we didn't move up to or past the target, it's because there * weren't enough characters to delete, e.g. the first character * of the line was a tp->offset character, and the user entered * ^D to move to the beginning of a line. An example of this is: * * :set ai sw=4iai^T^D * * Otherwise, count up the total spaces/tabs needed to get from the * beginning of the line (or the last non- character) to the * target. */ if (current >= target) spaces = tabs = 0; else { cno = current; tabs = 0; if (!O_ISSET(sp, O_EXPANDTAB)) { for (; cno + COL_OFF(cno, ts) <= target; ++tabs) cno += COL_OFF(cno, ts); } spaces = target - cno; } /* If we overwrote ai characters, reset the ai count. */ if (ai_reset) tp->ai = tabs + spaces; /* * Call txt_insch() to insert each character, so that we get the * correct effect when we add a to replace N . */ for (ch = '\t'; tabs > 0; --tabs) (void)txt_insch(sp, tp, &ch, 0); for (ch = ' '; spaces > 0; --spaces) (void)txt_insch(sp, tp, &ch, 0); return (0); } /* * txt_fc -- * File name completion. */ static int txt_fc(SCR *sp, TEXT *tp, int *redrawp) { struct stat sb; ARGS **argv; CHAR_T s_ch; EXCMD cmd; size_t indx, len, nlen, off; int argc, trydir; char *p, *t; trydir = 0; *redrawp = 0; /* * Find the beginning of this "word" -- if we're at the beginning * of the line, it's a special case. */ if (tp->cno == 1) { len = 0; p = tp->lb; } else retry: for (len = 0, off = tp->cno - 1, p = tp->lb + off;; --off, --p) { if (isblank(*p)) { ++p; break; } ++len; if (off == tp->ai || off == tp->offset) break; } /* * Get enough space for a wildcard character. * * XXX * This won't work for "foo\", since the \ will escape the expansion * character. I'm not sure if that's a bug or not... */ off = p - tp->lb; BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); p = tp->lb + off; s_ch = p[len]; p[len] = '*'; /* Build an ex command, and call the ex expansion routines. */ ex_cinit(&cmd, 0, 0, OOBLNO, OOBLNO, 0, NULL); if (argv_init(sp, &cmd)) return (1); if (argv_exp2(sp, &cmd, p, len + 1)) { p[len] = s_ch; return (0); } argc = cmd.argc; argv = cmd.argv; p[len] = s_ch; switch (argc) { case 0: /* No matches. */ if (!trydir) (void)sp->gp->scr_bell(sp); return (0); case 1: /* One match. */ /* If something changed, do the exchange. */ nlen = strlen(cmd.argv[0]->bp); if (len != nlen || memcmp(cmd.argv[0]->bp, p, len)) break; /* If haven't done a directory test, do it now. */ if (!trydir && !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) { p += len; goto isdir; } /* If nothing changed, period, ring the bell. */ if (!trydir) (void)sp->gp->scr_bell(sp); return (0); default: /* Multiple matches. */ *redrawp = 1; if (txt_fc_col(sp, argc, argv)) return (1); /* Find the length of the shortest match. */ for (nlen = cmd.argv[0]->len; --argc > 0;) { if (cmd.argv[argc]->len < nlen) nlen = cmd.argv[argc]->len; for (indx = 0; indx < nlen && cmd.argv[argc]->bp[indx] == cmd.argv[0]->bp[indx]; ++indx); nlen = indx; } break; } /* Overwrite the expanded text first. */ for (t = cmd.argv[0]->bp; len > 0 && nlen > 0; --len, --nlen) *p++ = *t++; /* If lost text, make the remaining old text overwrite characters. */ if (len) { tp->cno -= len; tp->owrite += len; } /* Overwrite any overwrite characters next. */ for (; nlen > 0 && tp->owrite > 0; --nlen, --tp->owrite, ++tp->cno) *p++ = *t++; /* Shift remaining text up, and move the cursor to the end. */ if (nlen) { off = p - tp->lb; BINC_RET(sp, tp->lb, tp->lb_len, tp->len + nlen); p = tp->lb + off; tp->cno += nlen; tp->len += nlen; if (tp->insert != 0) (void)memmove(p + nlen, p, tp->insert); while (nlen--) *p++ = *t++; } /* If a single match and it's a directory, retry it. */ if (argc == 1 && !stat(cmd.argv[0]->bp, &sb) && S_ISDIR(sb.st_mode)) { isdir: if (tp->owrite == 0) { off = p - tp->lb; BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); p = tp->lb + off; if (tp->insert != 0) (void)memmove(p + 1, p, tp->insert); ++tp->len; } else --tp->owrite; ++tp->cno; *p++ = '/'; trydir = 1; goto retry; } return (0); } /* * txt_fc_col -- * Display file names for file name completion. */ static int txt_fc_col(SCR *sp, int argc, ARGS **argv) { ARGS **av; CHAR_T *p; GS *gp; size_t base, cnt, col, colwidth, numrows, numcols, prefix, row; int nf = 0; int ac, reset; gp = sp->gp; /* Trim any directory prefix common to all of the files. */ if ((p = strrchr(argv[0]->bp, '/')) == NULL) prefix = 0; else { prefix = (p - argv[0]->bp) + 1; for (ac = argc - 1, av = argv + 1; ac > 0; --ac, ++av) if (av[0]->len < prefix || memcmp(av[0]->bp, argv[0]->bp, prefix)) { prefix = 0; break; } } /* * Figure out the column width for the longest name. Output is done on * 6 character "tab" boundaries for no particular reason. (Since we * don't output tab characters, we ignore the terminal's tab settings.) * Ignore the user's tab setting because we have no idea how reasonable * it is. */ for (ac = argc, av = argv, colwidth = 0; ac > 0; --ac, ++av) { for (col = 0, p = av[0]->bp + prefix; *p != '\0'; ++p) col += KEY_LEN(sp, *p); if (col > colwidth) colwidth = col; } colwidth += COL_OFF(colwidth, 6); /* * Writing to the bottom line of the screen is always turned off when * SC_TINPUT_INFO is set. Turn it back on, we know what we're doing. */ if (F_ISSET(sp, SC_TINPUT_INFO)) { reset = 1; F_CLR(sp, SC_TINPUT_INFO); } else reset = 0; #define CHK_INTR \ if (F_ISSET(gp, G_INTERRUPTED)) \ goto intr; /* If the largest file name is too large, just print them. */ if (colwidth > sp->cols) { for (ac = argc, av = argv; ac > 0; --ac, ++av) { p = msg_print(sp, av[0]->bp + prefix, &nf); (void)ex_printf(sp, "%s\n", p); if (F_ISSET(gp, G_INTERRUPTED)) break; } if (nf) FREE_SPACE(sp, (char *) p, 0); CHK_INTR; } else { /* Figure out the number of columns. */ numcols = (sp->cols - 1) / colwidth; if (argc > numcols) { numrows = argc / numcols; if (argc % numcols) ++numrows; } else numrows = 1; /* Display the files in sorted order. */ for (row = 0; row < numrows; ++row) { for (base = row, col = 0; col < numcols; ++col) { p = msg_print(sp, argv[base]->bp + prefix, &nf); cnt = ex_printf(sp, "%s", p); if (nf) FREE_SPACE(sp, (char *) p, 0); CHK_INTR; if ((base += numrows) >= argc) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); CHK_INTR; } (void)ex_puts(sp, "\n"); CHK_INTR; } (void)ex_puts(sp, "\n"); CHK_INTR; } (void)ex_fflush(sp); if (0) { intr: F_CLR(gp, G_INTERRUPTED); } if (reset) F_SET(sp, SC_TINPUT_INFO); return (0); } /* * txt_emark -- * Set the end mark on the line. */ static int txt_emark(SCR *sp, TEXT *tp, size_t cno) { CHAR_T ch, *kp; size_t chlen, nlen, olen; char *p; ch = CH_ENDMARK; /* * The end mark may not be the same size as the current character. * Don't let the line shift. */ nlen = KEY_LEN(sp, ch); if (tp->lb[cno] == '\t') (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); else olen = KEY_LEN(sp, tp->lb[cno]); /* * If the line got longer, well, it's weird, but it's easy. If * it's the same length, it's easy. If it got shorter, we have * to fix it up. */ if (olen > nlen) { BINC_RET(sp, tp->lb, tp->lb_len, tp->len + olen); chlen = olen - nlen; if (tp->insert != 0) memmove(tp->lb + cno + 1 + chlen, tp->lb + cno + 1, tp->insert); tp->len += chlen; tp->owrite += chlen; p = tp->lb + cno; if (tp->lb[cno] == '\t') for (cno += chlen; chlen--;) *p++ = ' '; else for (kp = KEY_NAME(sp, tp->lb[cno]), cno += chlen; chlen--;) *p++ = *kp++; } tp->lb[cno] = ch; return (vs_change(sp, tp->lno, LINE_RESET)); } /* * txt_err -- * Handle an error during input processing. */ static void txt_err(SCR *sp, TEXTH *tiqh) { recno_t lno; /* * The problem with input processing is that the cursor is at an * indeterminate position since some input may have been lost due * to a malloc error. So, try to go back to the place from which * the cursor started, knowing that it may no longer be available. * * We depend on at least one line number being set in the text * chain. */ for (lno = TAILQ_FIRST(tiqh)->lno; !db_exist(sp, lno) && lno > 0; --lno); sp->lno = lno == 0 ? 1 : lno; sp->cno = 0; /* Redraw the screen, just in case. */ F_SET(sp, SC_SCR_REDRAW); } /* * txt_hex -- * Let the user insert any character value they want. * * !!! * This is an extension. The pattern "^X[0-9a-fA-F]*" is a way * for the user to specify a character value which their keyboard * may not be able to enter. */ static int txt_hex(SCR *sp, TEXT *tp) { CHAR_T savec; size_t len, off; unsigned long value; char *p, *wp; /* * NULL-terminate the string. Since NULL isn't a legal hex value, * this should be okay, and lets us use a local routine, which * presumably understands the character set, to convert the value. */ savec = tp->lb[tp->cno]; tp->lb[tp->cno] = 0; /* Find the previous CH_HEX character. */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --p, --off, ++len) { if (*p == CH_HEX) { wp = p + 1; break; } /* Not on this line? Shouldn't happen. */ if (off == tp->ai || off == tp->offset) goto nothex; } /* If length of 0, then it wasn't a hex value. */ if (len == 0) goto nothex; /* Get the value. */ errno = 0; value = strtol(wp, NULL, 16); if (errno || value > MAX_CHAR_T) { nothex: tp->lb[tp->cno] = savec; return (0); } /* Restore the original character. */ tp->lb[tp->cno] = savec; /* Adjust the bookkeeping. */ tp->cno -= len; tp->len -= len; tp->lb[tp->cno - 1] = value; /* Copy down any overwrite characters. */ if (tp->owrite) memmove(tp->lb + tp->cno, tp->lb + tp->cno + len, tp->owrite); /* Copy down any insert characters. */ if (tp->insert) memmove(tp->lb + tp->cno + tp->owrite, tp->lb + tp->cno + tp->owrite + len, tp->insert); return (0); } /* * txt_insch -- * * !!! * Historic vi did a special screen optimization for tab characters. As an * example, for the keystrokes "iabcd0C", the tab overwrote the * rest of the string when it was displayed. * * Because early versions of this implementation redisplayed the entire line * on each keystroke, the "bcd" was pushed to the right as it ignored that * the user had "promised" to change the rest of the characters. However, * the historic vi implementation had an even worse bug: given the keystrokes * "iabcd0R", the "bcd" disappears, and magically reappears * on the second key. * * POSIX 1003.2 requires (will require) that this be fixed, specifying that * vi overwrite characters the user has committed to changing, on the basis * of the screen space they require, but that it not overwrite other characters. */ static int txt_insch(SCR *sp, TEXT *tp, CHAR_T *chp, unsigned int flags) { CHAR_T *kp, savech; size_t chlen, cno, copydown, olen, nlen; char *p; /* * The 'R' command does one-for-one replacement, because there's * no way to know how many characters the user intends to replace. */ if (LF_ISSET(TXT_REPLACE)) { if (tp->owrite) { --tp->owrite; tp->lb[tp->cno++] = *chp; return (0); } } else if (tp->owrite) { /* Overwrite a character. */ cno = tp->cno; /* * If the old or new characters are tabs, then the length of the * display depends on the character position in the display. We * don't even try to handle this here, just ask the screen. */ if (*chp == '\t') { savech = tp->lb[cno]; tp->lb[cno] = '\t'; (void)vs_columns(sp, tp->lb, tp->lno, &cno, &nlen); tp->lb[cno] = savech; } else nlen = KEY_LEN(sp, *chp); /* * Eat overwrite characters until we run out of them or we've * handled the length of the new character. If we only eat * part of an overwrite character, break it into its component * elements and display the remaining components. */ for (copydown = 0; nlen != 0 && tp->owrite != 0;) { --tp->owrite; if (tp->lb[cno] == '\t') (void)vs_columns(sp, tp->lb, tp->lno, &cno, &olen); else olen = KEY_LEN(sp, tp->lb[cno]); if (olen == nlen) { nlen = 0; break; } if (olen < nlen) { ++copydown; nlen -= olen; } else { BINC_RET(sp, tp->lb, tp->lb_len, tp->len + olen); chlen = olen - nlen; memmove(tp->lb + cno + 1 + chlen, tp->lb + cno + 1, tp->owrite + tp->insert); tp->len += chlen; tp->owrite += chlen; if (tp->lb[cno] == '\t') for (p = tp->lb + cno + 1; chlen--;) *p++ = ' '; else for (kp = KEY_NAME(sp, tp->lb[cno]) + nlen, p = tp->lb + cno + 1; chlen--;) *p++ = *kp++; nlen = 0; break; } } /* * If had to erase several characters, we adjust the total * count, and if there are any characters left, shift them * into position. */ if (copydown != 0 && (tp->len -= copydown) != 0) memmove(tp->lb + cno, tp->lb + cno + copydown, tp->owrite + tp->insert + copydown); /* If we had enough overwrite characters, we're done. */ if (nlen == 0) { tp->lb[tp->cno++] = *chp; return (0); } } /* Check to see if the character fits into the input buffer. */ BINC_RET(sp, tp->lb, tp->lb_len, tp->len + 1); ++tp->len; if (tp->insert) { /* Insert a character. */ if (tp->insert == 1) tp->lb[tp->cno + 1] = tp->lb[tp->cno]; else memmove(tp->lb + tp->cno + 1, tp->lb + tp->cno, tp->owrite + tp->insert); } tp->lb[tp->cno++] = *chp; return (0); } /* * txt_isrch -- * Do an incremental search. */ static int txt_isrch(SCR *sp, VICMD *vp, TEXT *tp, u_int8_t *is_flagsp) { MARK start; recno_t lno; unsigned int sf; /* If it's a one-line screen, we don't do incrementals. */ if (IS_ONELINE(sp)) { FL_CLR(*is_flagsp, IS_RUNNING); return (0); } /* * If the user erases back to the beginning of the buffer, there's * nothing to search for. Reset the cursor to the starting point. */ if (tp->cno <= 1) { vp->m_final = vp->m_start; return (0); } /* * If it's an RE quote character, and not quoted, ignore it until * we get another character. */ if (tp->lb[tp->cno - 1] == '\\' && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) return (0); /* * If it's a magic shell character, and not quoted, reset the cursor * to the starting point. */ if (strchr(O_STR(sp, O_SHELLMETA), tp->lb[tp->cno - 1]) != NULL && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) vp->m_final = vp->m_start; /* * If we see the search pattern termination character, then quit doing * an incremental search. There may be more, e.g., ":/foo/;/bar/", * and we can't handle that incrementally. Also, reset the cursor to * the original location, the ex search routines don't know anything * about incremental searches. */ if (tp->lb[0] == tp->lb[tp->cno - 1] && (tp->cno == 2 || tp->lb[tp->cno - 2] != '\\')) { vp->m_final = vp->m_start; FL_CLR(*is_flagsp, IS_RUNNING); return (0); } /* * Remember the input line and discard the special input map, * but don't overwrite the input line on the screen. */ lno = tp->lno; F_SET(VIP(sp), VIP_S_MODELINE); F_CLR(sp, SC_TINPUT | SC_TINPUT_INFO); if (txt_map_end(sp)) return (1); /* * Specify a starting point and search. If we find a match, move to * it and refresh the screen. If we didn't find the match, then we * beep the screen. When searching from the original cursor position, * we have to move the cursor, otherwise, we don't want to move the * cursor in case the text at the current position continues to match. */ if (FL_ISSET(*is_flagsp, IS_RESTART)) { start = vp->m_start; sf = SEARCH_SET; } else { start = vp->m_final; sf = SEARCH_INCR | SEARCH_SET; } if (tp->lb[0] == '/' ? !f_search(sp, &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf) : !b_search(sp, &start, &vp->m_final, tp->lb + 1, tp->cno - 1, NULL, sf)) { sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; FL_CLR(*is_flagsp, IS_RESTART); if (!KEYS_WAITING(sp) && vs_refresh(sp, 0)) return (1); } else FL_SET(*is_flagsp, IS_RESTART); /* Reinstantiate the special input map. */ if (txt_map_init(sp)) return (1); F_CLR(VIP(sp), VIP_S_MODELINE); F_SET(sp, SC_TINPUT | SC_TINPUT_INFO); /* Reset the line number of the input line. */ tp->lno = TMAP[0].lno; /* * If the colon command-line moved, i.e. the screen scrolled, * refresh the input line. * * XXX * We shouldn't be calling vs_line, here -- we need dirty bits * on entries in the SMAP array. */ if (lno != TMAP[0].lno) { if (vs_line(sp, &TMAP[0], NULL, NULL)) return (1); (void)sp->gp->scr_refresh(sp, 0); } return (0); } /* * txt_resolve -- * Resolve the input text chain into the file. */ static int txt_resolve(SCR *sp, TEXTH *tiqh, u_int32_t flags) { TEXT *tp; recno_t lno; int changed; /* * The first line replaces a current line, and all subsequent lines * are appended into the file. Resolve autoindented characters for * each line before committing it. If the latter causes the line to * change, we have to redisplay it, otherwise the information cached * about the line will be wrong. */ tp = TAILQ_FIRST(tiqh); if (LF_ISSET(TXT_AUTOINDENT)) txt_ai_resolve(sp, tp, &changed); else changed = 0; if (db_set(sp, tp->lno, tp->lb, tp->len) || (changed && vs_change(sp, tp->lno, LINE_RESET))) return (1); for (lno = tp->lno; (tp = TAILQ_NEXT(tp, q)); ++lno) { if (LF_ISSET(TXT_AUTOINDENT)) txt_ai_resolve(sp, tp, &changed); else changed = 0; if (db_append(sp, 0, lno, tp->lb, tp->len) || (changed && vs_change(sp, tp->lno, LINE_RESET))) return (1); } /* * Clear the input flag, the look-aside buffer is no longer valid. * Has to be done as part of text resolution, or upon return we'll * be looking at incorrect data. */ F_CLR(sp, SC_TINPUT); return (0); } /* * txt_showmatch -- * Show a character match. * * !!! * Historic vi tried to display matches even in the :colon command line. * I think not. */ static int txt_showmatch(SCR *sp, TEXT *tp) { VCS cs; MARK m; int cnt, endc, startc; /* * Do a refresh first, in case we haven't done one in awhile, * so the user can see what we're complaining about. */ UPDATE_POSITION(sp, tp); if (vs_refresh(sp, 1)) return (1); /* * We don't display the match if it's not on the screen. Find * out what the first character on the screen is. */ if (vs_sm_position(sp, &m, 0, P_TOP)) return (1); /* Initialize the getc() interface. */ cs.cs_lno = tp->lno; cs.cs_cno = tp->cno - 1; if (cs_init(sp, &cs)) return (1); startc = (endc = cs.cs_ch) == ')' ? '(' : '{'; /* Search for the match. */ for (cnt = 1;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags != 0) { if (cs.cs_flags == CS_EOF || cs.cs_flags == CS_SOF) { msgq(sp, M_BERR, "Unmatched %s", KEY_NAME(sp, endc)); return (0); } continue; } if (cs.cs_ch == endc) ++cnt; else if (cs.cs_ch == startc && --cnt == 0) break; } /* If the match is on the screen, move to it. */ if (cs.cs_lno < m.lno || (cs.cs_lno == m.lno && cs.cs_cno < m.cno)) return (0); sp->lno = cs.cs_lno; sp->cno = cs.cs_cno; if (vs_refresh(sp, 1)) return (1); /* Wait for timeout or character arrival. */ return (v_event_get(sp, NULL, O_VAL(sp, O_MATCHTIME) * 100, EC_TIMEOUT)); } /* * txt_margin -- * Handle margin wrap. */ static int txt_margin(SCR *sp, TEXT *tp, TEXT *wmtp, int *didbreak, u_int32_t flags) { size_t len, off; char *p; (void)sp; (void)tp; (void)wmtp; (void)didbreak; (void)flags; /* Find the nearest previous blank. */ for (off = tp->cno - 1, p = tp->lb + off, len = 0;; --off, --p, ++len) { if (isblank(*p)) break; /* * If reach the start of the line, there's nowhere to break. * * !!! * Historic vi belled each time a character was entered after * crossing the margin until a space was entered which could * be used to break the line. I don't as it tends to wake the * cats. */ if (off == tp->ai || off == tp->offset) { *didbreak = 0; return (0); } } /* * Store saved information about the rest of the line in the * wrapmargin TEXT structure. * * !!! * The offset field holds the length of the current characters * that the user entered, but which are getting split to the new * line -- it's going to be used to set the cursor value when we * move to the new line. */ wmtp->lb = p + 1; wmtp->offset = len; wmtp->insert = LF_ISSET(TXT_APPENDEOL) ? tp->insert - 1 : tp->insert; wmtp->owrite = tp->owrite; /* Correct current bookkeeping information. */ tp->cno -= len; if (LF_ISSET(TXT_APPENDEOL)) { tp->len -= len + tp->owrite + (tp->insert - 1); tp->insert = 1; } else { tp->len -= len + tp->owrite + tp->insert; tp->insert = 0; } tp->owrite = 0; /* * !!! * Delete any trailing whitespace from the current line. */ for (;; --p, --off) { if (!isblank(*p)) break; --tp->cno; --tp->len; if (off == tp->ai || off == tp->offset) break; } *didbreak = 1; return (0); } /* * txt_Rresolve -- * Resolve the input line for the 'R' command. */ static void txt_Rresolve(SCR *sp, TEXTH *tiqh, TEXT *tp, const size_t orig_len) { TEXT *ttp; size_t input_len, retain; char *p; /* * Check to make sure that the cursor hasn't moved beyond * the end of the line. */ if (tp->owrite == 0) return; /* * Calculate how many characters the user has entered, * plus the blanks erased by /s. */ input_len = 0; TAILQ_FOREACH(ttp, tiqh, q) { input_len += ttp == tp ? tp->cno : ttp->len + ttp->R_erase; } /* * If the user has entered less characters than the original line * was long, restore any overwritable characters to the original * characters. These characters are entered as "insert characters", * because they're after the cursor and we don't want to lose them. * (This is okay because the R command has no insert characters.) * We set owrite to 0 so that the insert characters don't get copied * to somewhere else, which means that the line and the length have * to be adjusted here as well. * * We have to retrieve the original line because the original pinned * page has long since been discarded. If it doesn't exist, that's * okay, the user just extended the file. */ if (input_len < orig_len) { retain = MINIMUM(tp->owrite, orig_len - input_len); if (db_get(sp, TAILQ_FIRST(tiqh)->lno, DBG_FATAL | DBG_NOCACHE, &p, NULL)) return; memcpy(tp->lb + tp->cno, p + input_len, retain); tp->len -= tp->owrite - retain; tp->owrite = 0; tp->insert += retain; } } /* * txt_nomorech -- * No more characters message. */ static void txt_nomorech(SCR *sp) { msgq(sp, M_BERR, "No more characters to erase"); } ================================================ FILE: vi/v_ulcase.c ================================================ /* $OpenBSD: v_ulcase.c,v 1.10 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int ulcase(SCR *, recno_t, CHAR_T *, size_t, size_t, size_t); /* * v_ulcase -- [count]~ * Toggle upper & lower case letters. * * !!! * Historic vi didn't permit ~ to cross newline boundaries. I can * think of no reason why it shouldn't, which at least lets the user * auto-repeat through a paragraph. * * !!! * In historic vi, the count was ignored. It would have been better * if there had been an associated motion, but it's too late to make * that the default now. * * PUBLIC: int v_ulcase(SCR *, VICMD *); */ int v_ulcase(SCR *sp, VICMD *vp) { recno_t lno; size_t cno, lcnt, len; unsigned long cnt; char *p; lno = vp->m_start.lno; cno = vp->m_start.cno; for (cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cnt > 0; cno = 0) { /* SOF is an error, EOF is an infinite count sink. */ if (db_get(sp, lno, 0, &p, &len)) { if (lno == 1) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } --lno; break; } /* Empty lines decrement the count by one. */ if (len == 0) { --cnt; vp->m_final.cno = 0; continue; } if (cno + cnt >= len) { lcnt = len - 1; cnt -= len - cno; vp->m_final.cno = len - 1; } else { lcnt = cno + cnt - 1; cnt = 0; vp->m_final.cno = lcnt + 1; } if (ulcase(sp, lno, p, len, cno, lcnt)) return (1); if (cnt > 0) ++lno; } vp->m_final.lno = lno; return (0); } /* * v_mulcase -- [count]~[count]motion * Toggle upper & lower case letters over a range. * * PUBLIC: int v_mulcase(SCR *, VICMD *); */ int v_mulcase(SCR *sp, VICMD *vp) { CHAR_T *p; size_t len; recno_t lno; for (lno = vp->m_start.lno;;) { if (db_get(sp, lno, DBG_FATAL, (char **) &p, &len)) return (1); if (len != 0 && ulcase(sp, lno, p, len, lno == vp->m_start.lno ? vp->m_start.cno : 0, !F_ISSET(vp, VM_LMODE) && lno == vp->m_stop.lno ? vp->m_stop.cno : len)) return (1); if (++lno > vp->m_stop.lno) break; } /* * XXX * I didn't create a new motion command when I added motion semantics * for ~. While that's the correct way to do it, that choice would * have required changes all over the vi directory for little gain. * Instead, we pretend it's a yank command. Note, this means that we * follow the cursor motion rules for yank commands, but that seems * reasonable to me. */ return (0); } /* * ulcase -- * Change part of a line's case. */ static int ulcase(SCR *sp, recno_t lno, CHAR_T *lp, size_t len, size_t scno, size_t ecno) { size_t blen; int change, rval; CHAR_T ch, *p, *t; char *bp; GET_SPACE_RET(sp, bp, blen, len); memmove(bp, lp, len); change = rval = 0; for (p = bp + scno, t = bp + ecno + 1; p < t; ++p) { ch = *(unsigned char *)p; if (islower(ch)) { *p = toupper(ch); change = 1; } else if (isupper(ch)) { *p = tolower(ch); change = 1; } } if (change && db_set(sp, lno, bp, len)) rval = 1; FREE_SPACE(sp, bp, blen); return (rval); } ================================================ FILE: vi/v_undo.c ================================================ /* $OpenBSD: v_undo.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_Undo -- U * Undo changes to this line. * * PUBLIC: int v_Undo(SCR *, VICMD *); */ int v_Undo(SCR *sp, VICMD *vp) { /* * Historically, U reset the cursor to the first column in the line * (not the first non-blank). This seems a bit non-intuitive, but, * considering that we may have undone multiple changes, anything * else (including the cursor position stored in the logging records) * is going to appear random. */ vp->m_final.cno = 0; /* * !!! * Set up the flags so that an immediately subsequent 'u' will roll * forward, instead of backward. In historic vi, a 'u' following a * 'U' redid all of the changes to the line. Given that the user has * explicitly discarded those changes by entering 'U', it seems likely * that the user wants something between the original and end forms of * the line, so starting to replay the changes seems the best way to * get to there. */ F_SET(sp->ep, F_UNDO); sp->ep->lundo = BACKWARD; return (log_setline(sp)); } /* * v_undo -- u * Undo the last change. * * PUBLIC: int v_undo(SCR *, VICMD *); */ int v_undo(SCR *sp, VICMD *vp) { EXF *ep; /* Set the command count. */ VIP(sp)->u_ccnt = sp->ccnt; /* * !!! * In historic vi, 'u' toggled between "undo" and "redo", i.e. 'u' * undid the last undo. However, if there has been a change since * the last undo/redo, we always do an undo. To make this work when * the user can undo multiple operations, we leave the old semantic * unchanged, but make '.' after a 'u' do another undo/redo operation. * This has two problems. * * The first is that 'u' didn't set '.' in historic vi. So, if a * user made a change, realized it was in the wrong place, does a * 'u' to undo it, moves to the right place and then does '.', the * change was reapplied. To make this work, we only apply the '.' * to the undo command if it's the command immediately following an * undo command. See vi/vi.c:getcmd() for the details. * * The second is that the traditional way to view the numbered cut * buffers in vi was to enter the commands "1pu.u.u.u. which will * no longer work because the '.' immediately follows the 'u' command. * Since we provide a much better method of viewing buffers, and * nobody can think of a better way of adding in multiple undo, this * remains broken. * * !!! * There is change to historic practice for the final cursor position * in this implementation. In historic vi, if an undo was isolated to * a single line, the cursor moved to the start of the change, and * then, subsequent 'u' commands would not move it again. (It has been * pointed out that users used multiple undo commands to get the cursor * to the start of the changed text.) Nvi toggles between the cursor * position before and after the change was made. One final issue is * that historic vi only did this if the user had not moved off of the * line before entering the undo command; otherwise, vi would move the * cursor to the most attractive position on the changed line. * * It would be difficult to match historic practice in this area. You * not only have to know that the changes were isolated to one line, * but whether it was the first or second undo command as well. And, * to completely match historic practice, we'd have to track users line * changes, too. This isn't worth the effort. */ ep = sp->ep; if (!F_ISSET(ep, F_UNDO)) { F_SET(ep, F_UNDO); ep->lundo = BACKWARD; } else if (!F_ISSET(vp, VC_ISDOT)) ep->lundo = ep->lundo == BACKWARD ? FORWARD : BACKWARD; switch (ep->lundo) { case BACKWARD: return (log_backward(sp, &vp->m_final)); case FORWARD: return (log_forward(sp, &vp->m_final)); default: abort(); } /* NOTREACHED */ } ================================================ FILE: vi/v_util.c ================================================ /* $OpenBSD: v_util.c,v 1.8 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_eof -- * Vi end-of-file error. * * PUBLIC: void v_eof(SCR *, MARK *); */ void v_eof(SCR *sp, MARK *mp) { recno_t lno; if (mp == NULL) v_emsg(sp, NULL, VIM_EOF); else { if (db_last(sp, &lno)) return; if (mp->lno >= lno) v_emsg(sp, NULL, VIM_EOF); else msgq(sp, M_BERR, "Movement past the end-of-file"); } } /* * v_eol -- * Vi end-of-line error. * * PUBLIC: void v_eol(SCR *, MARK *); */ void v_eol(SCR *sp, MARK *mp) { size_t len; if (mp == NULL) v_emsg(sp, NULL, VIM_EOL); else { if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len)) return; if (mp->cno == len - 1) v_emsg(sp, NULL, VIM_EOL); else msgq(sp, M_BERR, "Movement past the end-of-line"); } } /* * v_nomove -- * Vi no cursor movement error. * * PUBLIC: void v_nomove(SCR *); */ void v_nomove(SCR *sp) { msgq(sp, M_BERR, "No cursor movement made"); } /* * v_sof -- * Vi start-of-file error. * * PUBLIC: void v_sof(SCR *, MARK *); */ void v_sof(SCR *sp, MARK *mp) { if (mp == NULL || mp->lno == 1) msgq(sp, M_BERR, "Already at the beginning of the file"); else msgq(sp, M_BERR, "Movement past the beginning of the file"); } /* * v_sol -- * Vi start-of-line error. * * PUBLIC: void v_sol(SCR *); */ void v_sol(SCR *sp) { msgq(sp, M_BERR, "Already at the first column"); } /* * v_isempty -- * Return if the line contains nothing but white-space characters. * * PUBLIC: int v_isempty(char *, size_t); */ int v_isempty(char *p, size_t len) { for (; len--; ++p) if (!isblank(*p)) return (0); return (1); } /* * v_emsg -- * Display a few common vi messages. * * PUBLIC: void v_emsg(SCR *, char *, vim_t); */ void v_emsg(SCR *sp, char *p, vim_t which) { switch (which) { case VIM_COMBUF: msgq(sp, M_ERR, "Buffers should be specified before the command"); break; case VIM_EMPTY: msgq(sp, M_BERR, "The file is empty"); break; case VIM_EOF: msgq(sp, M_BERR, "Already at end-of-file"); break; case VIM_EOL: msgq(sp, M_BERR, "Already at end-of-line"); break; case VIM_NOCOM: case VIM_NOCOM_B: msgq(sp, which == VIM_NOCOM_B ? M_BERR : M_ERR, "%s isn't a vi command", p); break; case VIM_WRESIZE: msgq(sp, M_ERR, "Window resize interrupted text input mode"); break; case VIM_USAGE: msgq(sp, M_ERR, "Usage: %s", p); break; } } ================================================ FILE: vi/v_word.c ================================================ /* $OpenBSD: v_word.c,v 1.7 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * There are two types of "words". Bigwords are easy -- groups of anything * delimited by whitespace. Normal words are trickier. They are either a * group of characters, numbers and underscores, or a group of anything but, * delimited by whitespace. When for a word, if you're in whitespace, it's * easy, just remove the whitespace and go to the beginning or end of the * word. Otherwise, figure out if the next character is in a different group. * If it is, go to the beginning or end of that group, otherwise, go to the * beginning or end of the current group. The historic version of vi didn't * get this right, so, for example, there were cases where "4e" was not the * same as "eeee" -- in particular, single character words, and commands that * began in whitespace were almost always handled incorrectly. To get it right * you have to resolve the cursor after each search so that the look-ahead to * figure out what type of "word" the cursor is in will be correct. * * Empty lines, and lines that consist of only white-space characters count * as a single word, and the beginning and end of the file counts as an * infinite number of words. * * Movements associated with commands are different than movement commands. * For example, in "abc def", with the cursor on the 'a', "cw" is from * 'a' to 'c', while "w" is from 'a' to 'd'. In general, trailing white * space is discarded from the change movement. Another example is that, * in the same string, a "cw" on any white space character replaces that * single character, and nothing else. Ain't nothin' in here that's easy. * * One historic note -- in the original vi, the 'w', 'W' and 'B' commands * would treat groups of empty lines as individual words, i.e. the command * would move the cursor to each new empty line. The 'e' and 'E' commands * would treat groups of empty lines as a single word, i.e. the first use * would move past the group of lines. The 'b' command would just beep at * you, or, if you did it from the start of the line as part of a motion * command, go absolutely nuts. If the lines contained only white-space * characters, the 'w' and 'W' commands would just beep at you, and the 'B', * 'b', 'E' and 'e' commands would treat the group as a single word, and * the 'B' and 'b' commands will treat the lines as individual words. This * implementation treats all of these cases as a single white-space word. */ enum which {BIGWORD, LITTLEWORD}; static int bword(SCR *, VICMD *, enum which); static int eword(SCR *, VICMD *, enum which); static int fword(SCR *, VICMD *, enum which); /* * v_wordW -- [count]W * Move forward a bigword at a time. * * PUBLIC: int v_wordW(SCR *, VICMD *); */ int v_wordW(SCR *sp, VICMD *vp) { return (fword(sp, vp, BIGWORD)); } /* * v_wordw -- [count]w * Move forward a word at a time. * * PUBLIC: int v_wordw(SCR *, VICMD *); */ int v_wordw(SCR *sp, VICMD *vp) { return (fword(sp, vp, LITTLEWORD)); } /* * fword -- * Move forward by words. */ static int fword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; unsigned long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * If in white-space: * If the count is 1, and it's a change command, we're done. * Else, move to the first non-white-space character, which * counts as a single word move. If it's a motion command, * don't move off the end of the line. */ if (cs.cs_flags == CS_EMP || (cs.cs_flags == 0 && isblank(cs.cs_ch))) { if (ISMOTION(vp) && cs.cs_flags != CS_EMP && cnt == 1) { if (ISCMD(vp->rkp, 'c')) return (0); if (ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) { if (cs_fspace(sp, &cs)) return (1); goto ret; } } if (cs_fblank(sp, &cs)) return (1); --cnt; } /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'w' command, the definition of a word keeps * switching. */ if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } /* * If a motion command and we're at the end of the * last word, we're done. Delete and yank eat any * trailing blanks, but we don't move off the end * of the line regardless. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0 && ISMOTION(vp)) { if ((ISCMD(vp->rkp, 'd') || ISCMD(vp->rkp, 'y')) && cs_fspace(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || isblank(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Adjust the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; if (ISMOTION(vp) && cs.cs_flags == 0) --vp->m_stop.cno; /* * Non-motion commands move to the end of the range. Delete * and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_wordE -- [count]E * Move forward to the end of the bigword. * * PUBLIC: int v_wordE(SCR *, VICMD *); */ int v_wordE(SCR *sp, VICMD *vp) { return (eword(sp, vp, BIGWORD)); } /* * v_worde -- [count]e * Move forward to the end of the word. * * PUBLIC: int v_worde(SCR *, VICMD *); */ int v_worde(SCR *sp, VICMD *vp) { return (eword(sp, vp, LITTLEWORD)); } /* * eword -- * Move forward to the end of the word. */ static int eword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; unsigned long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the next character is whitespace, move past * it. (This doesn't count as a word move.) Stay at the character * past the current one, it sets word "state" for the 'e' command. */ if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) goto start; } if (cs_fblank(sp, &cs)) return (1); /* * Cyclically move to the next word -- this involves skipping * over word characters and then any trailing non-word characters. * Note, for the 'e' command, the definition of a word keeps * switching. */ start: if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } /* * When we reach the start of the word after the last * word, we're done. If we changed state, back up one * to the end of the previous word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_next(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_prev(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || isblank(cs.cs_ch)) if (cs_fblank(sp, &cs)) return (1); if (cs.cs_flags == CS_EOF) goto ret; } /* * If we didn't move, we must be at EOF. * * !!! * That's okay for motion commands, however. */ ret: if (!ISMOTION(vp) && cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_eof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * Non-motion commands move to the end of the range. * Delete and yank stay at the start, ignore others. */ vp->m_final = ISMOTION(vp) ? vp->m_start : vp->m_stop; return (0); } /* * v_WordB -- [count]B * Move backward a bigword at a time. * * PUBLIC: int v_wordB(SCR *, VICMD *); */ int v_wordB(SCR *sp, VICMD *vp) { return (bword(sp, vp, BIGWORD)); } /* * v_wordb -- [count]b * Move backward a word at a time. * * PUBLIC: int v_wordb(SCR *, VICMD *); */ int v_wordb(SCR *sp, VICMD *vp) { return (bword(sp, vp, LITTLEWORD)); } /* * bword -- * Move backward by words. */ static int bword(SCR *sp, VICMD *vp, enum which type) { enum { INWORD, NOTWORD } state; VCS cs; unsigned long cnt; cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; cs.cs_lno = vp->m_start.lno; cs.cs_cno = vp->m_start.cno; if (cs_init(sp, &cs)) return (1); /* * !!! * If in whitespace, or the previous character is whitespace, move * past it. (This doesn't count as a word move.) Stay at the * character before the current one, it sets word "state" for the * 'b' command. */ if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == 0 && !isblank(cs.cs_ch)) goto start; } if (cs_bblank(sp, &cs)) return (1); /* * Cyclically move to the beginning of the previous word -- this * involves skipping over word characters and then any trailing * non-word characters. Note, for the 'b' command, the definition * of a word keeps switching. */ start: if (type == BIGWORD) while (cnt--) { for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; } /* * When we reach the end of the word before the last * word, we're done. If we changed state, move forward * one to the end of the next word. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } else while (cnt--) { state = cs.cs_flags == 0 && inword(cs.cs_ch) ? INWORD : NOTWORD; for (;;) { if (cs_prev(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; if (cs.cs_flags != 0 || isblank(cs.cs_ch)) break; if (state == INWORD) { if (!inword(cs.cs_ch)) break; } else if (inword(cs.cs_ch)) break; } /* See comment above. */ if (cnt == 0) { if (cs.cs_flags == 0 && cs_next(sp, &cs)) return (1); break; } /* Eat whitespace characters. */ if (cs.cs_flags != 0 || isblank(cs.cs_ch)) if (cs_bblank(sp, &cs)) return (1); if (cs.cs_flags == CS_SOF) goto ret; } /* If we didn't move, we must be at SOF. */ ret: if (cs.cs_lno == vp->m_start.lno && cs.cs_cno == vp->m_start.cno) { v_sof(sp, &vp->m_start); return (1); } /* Set the end of the range for motion commands. */ vp->m_stop.lno = cs.cs_lno; vp->m_stop.cno = cs.cs_cno; /* * All commands move to the end of the range. Motion commands * adjust the starting point to the character before the current * one. * * !!! * The historic vi didn't get this right -- the `yb' command yanked * the right stuff and even updated the cursor value, but the cursor * was not actually updated on the screen. */ vp->m_final = vp->m_stop; if (ISMOTION(vp)) --vp->m_start.cno; return (0); } ================================================ FILE: vi/v_xchar.c ================================================ /* $OpenBSD: v_xchar.c,v 1.8 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_xchar -- [buffer] [count]x * Deletes the character(s) on which the cursor sits. * * PUBLIC: int v_xchar(SCR *, VICMD *); */ int v_xchar(SCR *sp, VICMD *vp) { size_t len; int isempty; if (db_eget(sp, vp->m_start.lno, NULL, &len, &isempty)) { if (isempty) goto nodel; return (1); } if (len == 0) { nodel: msgq(sp, M_BERR, "No characters to delete"); return (1); } /* * Delete from the cursor toward the end of line, w/o moving the * cursor. * * !!! * Note, "2x" at EOL isn't the same as "xx" because the left movement * of the cursor as part of the 'x' command isn't taken into account. * Historically correct. */ if (F_ISSET(vp, VC_C1SET)) vp->m_stop.cno += vp->count - 1; if (vp->m_stop.cno >= len - 1) { vp->m_stop.cno = len - 1; vp->m_final.cno = vp->m_start.cno ? vp->m_start.cno - 1 : 0; } else vp->m_final.cno = vp->m_start.cno; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (del(sp, &vp->m_start, &vp->m_stop, 0)); } /* * v_Xchar -- [buffer] [count]X * Deletes the character(s) immediately before the current cursor * position. * * PUBLIC: int v_Xchar(SCR *, VICMD *); */ int v_Xchar(SCR *sp, VICMD *vp) { unsigned long cnt; if (vp->m_start.cno == 0) { v_sol(sp); return (1); } cnt = F_ISSET(vp, VC_C1SET) ? vp->count : 1; if (cnt >= vp->m_start.cno) vp->m_start.cno = 0; else vp->m_start.cno -= cnt; --vp->m_stop.cno; vp->m_final.cno = vp->m_start.cno; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, 0)) return (1); return (del(sp, &vp->m_start, &vp->m_stop, 0)); } ================================================ FILE: vi/v_yank.c ================================================ /* $OpenBSD: v_yank.c,v 1.7 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_yank -- [buffer][count]y[count][motion] * [buffer][count]Y * Yank text (or lines of text) into a cut buffer. * * !!! * Historic vi moved the cursor to the from MARK if it was before the current * cursor and on a different line, e.g., "yk" moves the cursor but "yj" and * "yl" do not. Unfortunately, it's too late to change this now. Matching * the historic semantics isn't easy. The line number was always changed and * column movement was usually relative. However, "y'a" moved the cursor to * the first non-blank of the line marked by a, while "y`a" moved the cursor * to the line and column marked by a. Hopefully, the motion component code * got it right... Unlike delete, we make no adjustments here. * * PUBLIC: int v_yank(SCR *, VICMD *); */ int v_yank(SCR *sp, VICMD *vp) { size_t len; if (cut(sp, F_ISSET(vp, VC_BUFFER) ? &vp->buffer : NULL, &vp->m_start, &vp->m_stop, F_ISSET(vp, VM_LMODE) ? CUT_LINEMODE : 0)) return (1); sp->rptlines[L_YANKED] += (vp->m_stop.lno - vp->m_start.lno) + 1; /* * One special correction, in case we've deleted the current line or * character. We check it here instead of checking in every command * that can be a motion component. */ if (db_get(sp, vp->m_final.lno, DBG_FATAL, NULL, &len)) return (1); /* * !!! * Cursor movements, other than those caused by a line mode command * moving to another line, historically reset the relative position. * * This currently matches the check made in v_delete(), I'm hoping * that they should be consistent... */ if (!F_ISSET(vp, VM_LMODE)) { F_CLR(vp, VM_RCM_MASK); F_SET(vp, VM_RCM_SET); /* Make sure the set cursor position exists. */ if (vp->m_final.cno >= len) vp->m_final.cno = len ? len - 1 : 0; } return (0); } ================================================ FILE: vi/v_z.c ================================================ /* $OpenBSD: v_z.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_z -- [count]z[count][-.+^] * Move the screen. * * PUBLIC: int v_z(SCR *, VICMD *); */ int v_z(SCR *sp, VICMD *vp) { recno_t lno; unsigned int value; /* * The first count is the line to use. If the value doesn't * exist, use the last line. */ if (F_ISSET(vp, VC_C1SET)) { lno = vp->count; if (!db_exist(sp, lno) && db_last(sp, &lno)) return (1); } else lno = vp->m_start.lno; /* Set default return cursor line. */ vp->m_final.lno = lno; vp->m_final.cno = vp->m_start.cno; /* * The second count is the displayed window size, i.e. the 'z' command * is another way to get artificially small windows. Note, you can't * grow beyond the size of the window. * * !!! * A window size of 0 was historically allowed, and simply ignored. * This could be much more simply done by modifying the value of the * O_WINDOW option, but that's not how it worked historically. */ if (F_ISSET(vp, VC_C2SET) && vp->count2 != 0) { if (vp->count2 > O_VAL(sp, O_WINDOW)) vp->count2 = O_VAL(sp, O_WINDOW); if (vs_crel(sp, vp->count2)) return (1); } switch (vp->character) { case '-': /* Put the line at the bottom. */ if (vs_sm_fill(sp, lno, P_BOTTOM)) return (1); break; case '.': /* Put the line in the middle. */ if (vs_sm_fill(sp, lno, P_MIDDLE)) return (1); break; case '+': /* * If the user specified a line number, put that line at the * top and move the cursor to it. Otherwise, scroll forward * a screen from the current screen. */ if (F_ISSET(vp, VC_C1SET)) { if (vs_sm_fill(sp, lno, P_TOP)) return (1); if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) return (1); } else if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_PLUS)) return (1); break; case '^': /* * If the user specified a line number, put that line at the * bottom, move the cursor to it, and then display the screen * before that one. Otherwise, scroll backward a screen from * the current screen. * * !!! * Note, we match the off-by-one characteristics of historic * vi, here. */ if (F_ISSET(vp, VC_C1SET)) { if (vs_sm_fill(sp, lno, P_BOTTOM)) return (1); if (vs_sm_position(sp, &vp->m_final, 0, P_TOP)) return (1); if (vs_sm_fill(sp, vp->m_final.lno, P_BOTTOM)) return (1); } else if (vs_sm_scroll(sp, &vp->m_final, sp->t_rows, Z_CARAT)) return (1); break; default: /* Put the line at the top for . */ value = KEY_VAL(sp, vp->character); if (value != K_CR && value != K_NL) { v_emsg(sp, vp->kp->usage, VIM_USAGE); return (1); } if (vs_sm_fill(sp, lno, P_TOP)) return (1); break; } return (0); } /* * vs_crel -- * Change the relative size of the current screen. * * PUBLIC: int vs_crel(SCR *, long); */ int vs_crel(SCR *sp, long count) { if (sp == NULL) return (0); sp->t_minrows = sp->t_rows = count; if (sp->t_rows > sp->rows - 1) sp->t_minrows = sp->t_rows = sp->rows - 1; if (HMAP != NULL) TMAP = HMAP + (sp->t_rows - 1); F_SET(sp, SC_SCR_REDRAW); return (0); } ================================================ FILE: vi/v_zexit.c ================================================ /* $OpenBSD: v_zexit.c,v 1.6 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * v_zexit -- ZZ * Save the file and exit. * * PUBLIC: int v_zexit(SCR *, VICMD *); */ int v_zexit(SCR *sp, VICMD *vp) { (void)sp; (void)vp; /* Write back any modifications. */ if (F_ISSET(sp->ep, F_MODIFIED) && file_write(sp, NULL, NULL, NULL, FS_ALL)) return (1); /* Check to make sure it's not a temporary file. */ if (file_m3(sp, 0)) return (1); /* Check for more files to edit. */ if (ex_ncheck(sp, 0)) return (1); F_SET(sp, SC_EXIT); return (0); } ================================================ FILE: vi/vi.c ================================================ /* $OpenBSD: vi.c,v 1.23 2022/02/20 19:45:51 tb Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" typedef enum { GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK } gcret_t; static VIKEYS const *v_alias(SCR *, VICMD *, VIKEYS const *); static gcret_t v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *); static int v_count(SCR *, CHAR_T, unsigned long *); static void v_dtoh(SCR *); static int v_init(SCR *); static gcret_t v_key(SCR *, int, EVENT *, u_int32_t); static int v_keyword(SCR *); static int v_motion(SCR *, VICMD *, VICMD *, int *); #if defined(DEBUG) && defined(COMLOG) static void v_comlog(SCR *, VICMD *); #endif /* if defined(DEBUG) && defined(COMLOG) */ /* * Side-effect: * The dot structure can be set by the underlying vi functions, * see v_Put() and v_put(). */ #define DOT (&VIP(sp)->sdot) #define DOTMOTION (&VIP(sp)->sdotmotion) /* * vi -- * Main vi command loop. * * PUBLIC: int vi(SCR **); */ int vi(SCR **spp) { GS *gp; MARK abs; SCR *next, *sp; VICMD cmd, *vp; VI_PRIVATE *vip; int comcount, mapped, rval; int ret; /* Get the first screen. */ sp = *spp; gp = sp->gp; /* Initialize the command structure. */ vp = &cmd; memset(vp, 0, sizeof(VICMD)); /* Reset strange attraction. */ F_SET(vp, VM_RCM_SET); /* Initialize the vi screen. */ if (v_init(sp)) return (1); /* Set the focus. */ (void)sp->gp->scr_rename(sp, sp->frp->name, 1); for (vip = VIP(sp), rval = 0;;) { /* Resolve messages. */ if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0)) goto ret; /* * If not skipping a refresh, return to command mode and * refresh the screen. */ if (F_ISSET(vip, VIP_S_REFRESH)) F_CLR(vip, VIP_S_REFRESH); else { sp->showmode = SM_COMMAND; if (vs_refresh(sp, 0)) goto ret; } /* Set the new favorite position. */ if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) { F_CLR(vip, VIP_RCM_LAST); (void)vs_column(sp, &sp->rcm); } /* * If not currently in a map, log the cursor position, * and set a flag so that this command can become the * DOT command. */ if (MAPPED_KEYS_WAITING(sp)) mapped = 1; else { if (log_cursor(sp)) goto err; mapped = 0; } /* * There may be an ex command waiting, and we returned here * only because we exited a screen or file. In this case, * we simply go back into the ex parser. */ if (EXCMD_RUNNING(gp)) { vp->kp = &vikeys[':']; goto ex_continue; } /* Refresh the command structure. */ memset(vp, 0, sizeof(VICMD)); /* * We get a command, which may or may not have an associated * motion. If it does, we get it too, calling its underlying * function to get the resulting mark. We then call the * command setting the cursor to the resulting mark. * * !!! * Vi historically flushed mapped characters on error, but * entering extra characters at the beginning of * a map wasn't considered an error -- in fact, users would * put leading characters in maps to clean up vi * state before the map was interpreted. Beauty! */ switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) { case GC_ERR: goto err; case GC_ERR_NOFLUSH: goto gc_err_noflush; case GC_EVENT: if (v_event_exec(sp, vp)) goto err; goto gc_event; case GC_FATAL: goto ret; case GC_INTERRUPT: goto intr; case GC_OK: break; } /* Check for security setting. */ if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) { ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE); goto err; } /* * Historical practice: if a dot command gets a new count, * any motion component goes away, i.e. "d3w2." deletes a * total of 5 words. */ if (F_ISSET(vp, VC_ISDOT) && comcount) DOTMOTION->count = 1; /* Copy the key flags into the local structure. */ F_SET(vp, vp->kp->flags); /* Prepare to set the previous context. */ if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) { abs.lno = sp->lno; abs.cno = sp->cno; } /* * Set the three cursor locations to the current cursor. The * underlying routines don't bother if the cursor doesn't move. * This also handles line commands (e.g. Y) defaulting to the * current line. */ vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno; vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno; /* * Do any required motion; v_motion sets the from MARK and the * line mode flag, as well as the VM_RCM flags. */ if (F_ISSET(vp, V_MOTION) && v_motion(sp, DOTMOTION, vp, &mapped)) { if (INTERRUPTED(sp)) goto intr; goto err; } /* * If a count is set and the command is line oriented, set the * to MARK here relative to the cursor/from MARK. This is for * commands that take both counts and motions, i.e. "4yy" and * "y%". As there's no way the command can know which the user * did, we have to do it here. (There are commands that are * line oriented and that take counts ("#G", "#H"), for which * this calculation is either completely meaningless or wrong. * Each command must validate the value for itself. */ if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE)) vp->m_stop.lno += vp->count - 1; /* Increment the command count. */ ++sp->ccnt; #if defined(DEBUG) && defined(COMLOG) v_comlog(sp, vp); #endif /* if defined(DEBUG) && defined(COMLOG) */ /* Call the function. */ ex_continue: if (strchr(O_STR(sp, O_IMKEY), vp->key)) sp->gp->scr_imctrl(sp, IMCTRL_ON); ret = vp->kp->func(sp, vp); if (strchr(O_STR(sp, O_IMKEY), vp->key)) sp->gp->scr_imctrl(sp, IMCTRL_OFF); if (ret) goto err; gc_event: #ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(gp, G_TMP_INUSE)) { F_CLR(gp, G_TMP_INUSE); msgq(sp, M_ERR, "vi: temporary buffer not released"); } #endif /* ifdef DEBUG */ /* * If we're exiting this screen, move to the next one, or, if * there aren't any more, return to the main editor loop. The * ordering is careful, don't discard the contents of sp until * the end. */ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) goto ret; if (vs_discard(sp, &next)) goto ret; if (next == NULL && vs_swap(sp, &next, NULL)) goto ret; *spp = next; if (screen_end(sp)) goto ret; if (next == NULL) break; /* Switch screens, change focus. */ sp = next; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); continue; } /* * Set the dot command structure. * * !!! * Historically, commands which used mapped keys did not * set the dot command, with the exception of the text * input commands. */ if (F_ISSET(vp, V_DOT) && !mapped) { *DOT = cmd; F_SET(DOT, VC_ISDOT); /* * If a count was supplied for both the command and * its motion, the count was used only for the motion. * Turn the count back on for the dot structure. */ if (F_ISSET(vp, VC_C1RESET)) F_SET(DOT, VC_C1SET); /* VM flags aren't retained. */ F_CLR(DOT, VM_COMMASK | VM_RCM_MASK); } /* * Some vi row movements are "attracted" to the last position * set, i.e. the VM_RCM commands are moths to the VM_RCM_SET * commands' candle. If the movement is to the EOL the vi * command handles it. If it's to the beginning, we handle it * here. * * Note, some commands (e.g. _, ^) don't set the VM_RCM_SETFNB * flag, but do the work themselves. The reason is that they * have to modify the column in case they're being used as a * motion component. Other similar commands (e.g. +, -) don't * have to modify the column because they are always line mode * operations when used as motions, so the column number isn't * of any interest. * * Does this totally violate the screen and editor layering? * You betcha. As they say, if you think you understand it, * you don't. */ switch (F_ISSET(vp, VM_RCM_MASK)) { case 0: case VM_RCM_SET: break; case VM_RCM: vp->m_final.cno = vs_rcm(sp, vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST)); break; case VM_RCM_SETLAST: F_SET(vip, VIP_RCM_LAST); break; case VM_RCM_SETFNB: vp->m_final.cno = 0; /* FALLTHROUGH */ case VM_RCM_SETNNB: if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno)) goto err; break; default: abort(); } /* Update the cursor. */ sp->lno = vp->m_final.lno; sp->cno = vp->m_final.cno; /* * Set the absolute mark -- set even if a tags or similar * command, since the tag may be moving to the same file. */ if ((F_ISSET(vp, V_ABS) || (F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) || (F_ISSET(vp, V_ABS_C) && (sp->lno != abs.lno || sp->cno != abs.cno))) && mark_set(sp, ABSMARK1, &abs, 1)) goto err; if (0) { err: if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_BERR, "Vi command failed: mapped keys discarded"); } /* * Check and clear interrupts. There's an obvious race, but * it's not worth fixing. */ gc_err_noflush: if (INTERRUPTED(sp)) { intr: CLR_INTERRUPT(sp); if (v_event_flush(sp, CH_MAPPED)) msgq(sp, M_ERR, "Interrupted: mapped keys discarded"); else msgq(sp, M_ERR, "Interrupted"); } /* If the last command switched screens, update. */ if (F_ISSET(sp, SC_SSWITCH)) { F_CLR(sp, SC_SSWITCH); /* * If the current screen is still displayed, it will * need a new status line. */ F_SET(sp, SC_STATUS); /* Switch screens, change focus. */ sp = sp->nextdisp; vip = VIP(sp); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); /* Don't trust the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Refresh so we can display messages. */ if (vs_refresh(sp, 1)) return (1); } /* If the last command switched files, change focus. */ if (F_ISSET(sp, SC_FSWITCH)) { F_CLR(sp, SC_FSWITCH); F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_CENTER); (void)sp->gp->scr_rename(sp, sp->frp->name, 1); } /* Sync recovery if changes were made. */ if (F_ISSET(sp->ep, F_RCV_SYNC)) rcv_sync(sp, 0); /* If leaving vi, return to the main editor loop. */ if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) { *spp = sp; v_dtoh(sp); break; } } if (0) ret: rval = 1; return (rval); } #define KEY(key, ec_flags) { \ if ((gcret = v_key(sp, 0, &ev, (ec_flags))) != GC_OK) \ return (gcret); \ if (ev.e_value == K_ESCAPE) \ goto esc; \ if (F_ISSET(&ev.e_ch, CH_MAPPED)) \ *mappedp = 1; \ (key) = ev.e_c; \ } /* * The O_TILDEOP option makes the ~ command take a motion instead * of a straight count. This is the replacement structure we use * instead of the one currently in the VIKEYS table. * * XXX * This should probably be deleted -- it's not all that useful, and * we get help messages wrong. */ VIKEYS const tmotion = { v_mulcase, V_CNT|V_DOT|V_MOTION|VM_RCM_SET, "[count]~[count]motion", " ~ change case to motion" }; /* * v_cmd -- * * The command structure for vi is less complex than ex (and don't think * I'm not grateful!) The command syntax is: * * [count] [buffer] [count] key [[motion] | [buffer] [character]] * * and there are several special cases. The motion value is itself a vi * command, with the syntax: * * [count] key [character] */ static gcret_t v_cmd(SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, int *comcountp, int *mappedp) { enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart; EVENT ev; VIKEYS const *kp; gcret_t gcret; unsigned int flags; CHAR_T key; char *s; /* * Get a key. * * cancels partial commands, i.e. a command where at least * one non-numeric character has been entered. Otherwise, it beeps * the terminal. * * !!! * POSIX 1003.2-1992 explicitly disallows cancelling commands where * all that's been entered is a number, requiring that the terminal * be alerted. */ cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL; if ((gcret = v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) { if (gcret == GC_EVENT) vp->ev = ev; return (gcret); } if (ev.e_value == K_ESCAPE) goto esc; if (F_ISSET(&ev.e_ch, CH_MAPPED)) *mappedp = 1; key = ev.e_c; if (ismotion == NULL) cpart = NOTPARTIAL; /* Pick up optional buffer. */ if (key == '"') { cpart = ISPARTIAL; if (ismotion != NULL) { v_emsg(sp, NULL, VIM_COMBUF); return (GC_ERR); } KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); KEY(key, EC_MAPCOMMAND); } /* * Pick up optional count, where a leading 0 is not a count, * it's a command. */ if (isdigit(key) && key != '0') { if (v_count(sp, key, &vp->count)) return (GC_ERR); F_SET(vp, VC_C1SET); *comcountp = 1; KEY(key, EC_MAPCOMMAND); } else *comcountp = 0; /* Pick up optional buffer. */ if (key == '"') { cpart = ISPARTIAL; if (F_ISSET(vp, VC_BUFFER)) { msgq(sp, M_ERR, "Only one buffer may be specified"); return (GC_ERR); } if (ismotion != NULL) { v_emsg(sp, NULL, VIM_COMBUF); return (GC_ERR); } KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); KEY(key, EC_MAPCOMMAND); } /* Check for an OOB command key. */ cpart = ISPARTIAL; if (key > MAXVIKEY) { v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM); return (GC_ERR); } kp = &vikeys[vp->key = key]; /* * !!! * Historically, D accepted and then ignored a count. Match it. */ if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) { *comcountp = 0; vp->count = 0; F_CLR(vp, VC_C1SET); } /* Check for command aliases. */ if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL) return (GC_ERR); /* The tildeop option makes the ~ command take a motion. */ if (key == '~' && O_ISSET(sp, O_TILDEOP)) kp = &tmotion; vp->kp = kp; /* * Find the command. The only legal command with no underlying * function is dot. It's historic practice that doesn't * just erase the preceding number, it beeps the terminal as well. * It's a common problem, so just beep the terminal unless verbose * was set. */ if (kp->func == NULL) { if (key != '.') { v_emsg(sp, KEY_NAME(sp, key), ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM); return (GC_ERR); } /* If called for a motion command, stop now. */ if (dp == NULL) goto usage; /* * !!! * If a '.' is immediately entered after an undo command, we * replay the log instead of redoing the last command. This * is necessary because 'u' can't set the dot command -- see * vi/v_undo.c:v_undo for details. */ if (VIP(sp)->u_ccnt == sp->ccnt) { vp->kp = &vikeys['u']; F_SET(vp, VC_ISDOT); return (GC_OK); } /* Otherwise, a repeatable command must have been executed. */ if (!F_ISSET(dp, VC_ISDOT)) { msgq(sp, M_ERR, "No command to repeat"); return (GC_ERR); } /* Set new count/buffer, if any, and return. */ if (F_ISSET(vp, VC_C1SET)) { F_SET(dp, VC_C1SET); dp->count = vp->count; } if (F_ISSET(vp, VC_BUFFER)) dp->buffer = vp->buffer; *vp = *dp; return (GC_OK); } /* Set the flags based on the command flags. */ flags = kp->flags; /* Check for illegal count. */ if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT)) goto usage; /* Illegal motion command. */ if (ismotion == NULL) { /* Illegal buffer. */ if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER)) goto usage; /* Required buffer. */ if (LF_ISSET(V_RBUF)) { KEY(vp->buffer, 0); F_SET(vp, VC_BUFFER); } } /* * Special case: '[', ']' and 'Z' commands. Doesn't the fact that * the *single* characters don't mean anything but the *doubled* * characters do, just frost your shorts? */ if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') { /* * Historically, half entered [[, ]] or Z commands weren't * cancelled by , the terminal was beeped instead. * POSIX.2-1992 probably didn't notice, and requires that * they be cancelled instead of beeping. Seems fine to me. * * Don't set the EC_MAPCOMMAND flag, apparently ] is a popular * vi meta-character, and we don't want the user to wait while * we time out a possible mapping. This *appears* to match * historic vi practice, but with mapping characters, you Just * Never Know. */ KEY(key, 0); if (vp->key != key) { usage: if (ismotion == NULL) s = kp->usage; else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP)) s = tmotion.usage; else s = vikeys[ismotion->key].usage; v_emsg(sp, s, VIM_USAGE); return (GC_ERR); } } /* Special case: 'z' command. */ if (vp->key == 'z') { KEY(vp->character, 0); if (isdigit(vp->character)) { if (v_count(sp, vp->character, &vp->count2)) return (GC_ERR); F_SET(vp, VC_C2SET); KEY(vp->character, 0); } } /* * Commands that have motion components can be doubled to * imply the current line. */ if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) { msgq(sp, M_ERR, "%s may not be used as a motion command", KEY_NAME(sp, key)); return (GC_ERR); } /* Required character. */ if (LF_ISSET(V_CHAR)) { if (strchr(O_STR(sp, O_IMKEY), vp->key)) sp->gp->scr_imctrl(sp, IMCTRL_ON); KEY(vp->character, 0); if (strchr(O_STR(sp, O_IMKEY), vp->key)) sp->gp->scr_imctrl(sp, IMCTRL_OFF); } /* Get any associated cursor word. */ if (F_ISSET(kp, V_KEYW) && v_keyword(sp)) return (GC_ERR); return (GC_OK); esc: switch (cpart) { case COMMANDMODE: msgq(sp, M_BERR, "Already in command mode"); return (GC_ERR_NOFLUSH); case ISPARTIAL: break; case NOTPARTIAL: (void)sp->gp->scr_bell(sp); break; } return (GC_ERR); } /* * v_motion -- * * Get resulting motion mark. */ static int v_motion(SCR *sp, VICMD *dm, VICMD *vp, int *mappedp) { VICMD motion; size_t len; unsigned long cnt; unsigned int flags; int tilde_reset, notused; #ifdef IMKEY int rval; #endif /* ifdef IMKEY */ /* * If '.' command, use the dot motion, else get the motion command. * Clear any line motion flags, the subsequent motion isn't always * the same, i.e. "/aaa" may or may not be a line motion. */ if (F_ISSET(vp, VC_ISDOT)) { motion = *dm; F_SET(&motion, VC_ISDOT); F_CLR(&motion, VM_COMMASK); } else { memset(&motion, 0, sizeof(VICMD)); if (v_cmd(sp, NULL, &motion, vp, ¬used, mappedp) != GC_OK) return (1); } /* * A count may be provided both to the command and to the motion, in * which case the count is multiplicative. For example, "3y4y" is the * same as "12yy". This count is provided to the motion command and * not to the regular function. */ cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1; if (F_ISSET(vp, VC_C1SET)) { motion.count *= vp->count; F_SET(&motion, VC_C1SET); /* * Set flags to restore the original values of the command * structure so dot commands can change the count values, * e.g. "2dw" "3." deletes a total of five words. */ F_CLR(vp, VC_C1SET); F_SET(vp, VC_C1RESET); } /* * Some commands can be repeated to indicate the current line. In * this case, or if the command is a "line command", set the flags * appropriately. If not a doubled command, run the function to get * the resulting mark. */ if (vp->key == motion.key) { F_SET(vp, VM_LDOUBLE | VM_LMODE); /* Set the origin of the command. */ vp->m_start.lno = sp->lno; vp->m_start.cno = 0; /* * Set the end of the command. * * If the current line is missing, i.e. the file is empty, * historic vi permitted a "cc" or "!!" command to insert * text. */ vp->m_stop.lno = sp->lno + motion.count - 1; if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) { if (vp->m_stop.lno != 1 || (vp->key != 'c' && vp->key != '!')) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } vp->m_stop.cno = 0; } else vp->m_stop.cno = len ? len - 1 : 0; } else { /* * Motion commands change the underlying movement (*snarl*). * For example, "l" is illegal at the end of a line, but "dl" * is not. Set flags so the function knows the situation. */ motion.rkp = vp->kp; /* * XXX * Use yank instead of creating a new motion command, it's a * lot easier for now. */ if (vp->kp == &tmotion) { tilde_reset = 1; vp->kp = &vikeys['y']; } else tilde_reset = 0; /* * Copy the key flags into the local structure, except for the * RCM flags -- the motion command will set the RCM flags in * the vp structure if necessary. This means that the motion * command is expected to determine where the cursor ends up! * However, we save off the current RCM mask and restore it if * it no RCM flags are set by the motion command, with a small * modification. * * We replace the VM_RCM_SET flag with the VM_RCM flag. This * is so that cursor movement doesn't set the relative position * unless the motion command explicitly specified it. This * appears to match historic practice, but I've never been able * to develop a hard-and-fast rule. */ flags = F_ISSET(vp, VM_RCM_MASK); if (LF_ISSET(VM_RCM_SET)) { LF_SET(VM_RCM); LF_CLR(VM_RCM_SET); } F_CLR(vp, VM_RCM_MASK); F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK); /* * Set the three cursor locations to the current cursor. This * permits commands like 'j' and 'k', that are line oriented * motions and have special cursor suck semantics when they are * used as standalone commands, to ignore column positioning. */ motion.m_final.lno = motion.m_stop.lno = motion.m_start.lno = sp->lno; motion.m_final.cno = motion.m_stop.cno = motion.m_start.cno = sp->cno; /* Run the function. */ #ifndef IMKEY if ((motion.kp->func)(sp, &motion)) return (1); #else if (strchr(O_STR(sp, O_IMKEY), motion.key)) imreset(sp); rval = (motion.kp->func)(sp, &motion); if (strchr(O_STR(sp, O_IMKEY), motion.key)) imoff(sp); if (rval) return (1); #endif /* ifndef IMKEY */ /* * If the current line is missing, i.e. the file is empty, * historic vi allowed "c" or "!" to insert * text. Otherwise fail -- most motion commands will have * already failed, but some, e.g. G, succeed in empty files. */ if (!db_exist(sp, vp->m_stop.lno)) { if (vp->m_stop.lno != 1 || (vp->key != 'c' && vp->key != '!')) { v_emsg(sp, NULL, VIM_EMPTY); return (1); } vp->m_stop.cno = 0; } /* * XXX * See above. */ if (tilde_reset) vp->kp = &tmotion; /* * Copy cut buffer, line mode and cursor position information * from the motion command structure, i.e. anything that the * motion command can set for us. The commands can flag the * movement as a line motion (see v_sentence) as well as set * the VM_RCM_* flags explicitly. */ F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK)); /* * If the motion command set no relative motion flags, use * the (slightly) modified previous values. */ if (!F_ISSET(vp, VM_RCM_MASK)) F_SET(vp, flags); /* * Commands can change behaviors based on the motion command * used, for example, the ! command repeated the last bang * command if N or n was used as the motion. */ vp->rkp = motion.kp; /* * Motion commands can reset all of the cursor information. * If the motion is in the reverse direction, switch the * from and to MARK's so that it's in a forward direction. * Motions are from the from MARK to the to MARK (inclusive). */ if (motion.m_start.lno > motion.m_stop.lno || (motion.m_start.lno == motion.m_stop.lno && motion.m_start.cno > motion.m_stop.cno)) { vp->m_start = motion.m_stop; vp->m_stop = motion.m_start; } else { vp->m_start = motion.m_start; vp->m_stop = motion.m_stop; } vp->m_final = motion.m_final; } /* * If the command sets dot, save the motion structure. The motion * count was changed above and needs to be reset, that's why this * is done here, and not in the calling routine. */ if (F_ISSET(vp->kp, V_DOT)) { *dm = motion; dm->count = cnt; } return (0); } /* * v_init -- * Initialize the vi screen. */ static int v_init(SCR *sp) { GS *gp; VI_PRIVATE *vip; gp = sp->gp; vip = VIP(sp); /* Switch into vi. */ if (gp->scr_screen(sp, SC_VI)) return (1); (void)gp->scr_attr(sp, SA_ALTERNATE, 1); F_CLR(sp, SC_EX | SC_SCR_EX); F_SET(sp, SC_VI); /* * Initialize screen values. * * Small windows: see vs_refresh(), section 6a. * * Setup: * t_minrows is the minimum rows to display * t_maxrows is the maximum rows to display (rows - 1) * t_rows is the rows currently being displayed */ sp->rows = vip->srows = O_VAL(sp, O_LINES); sp->cols = O_VAL(sp, O_COLUMNS); sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW); /* * To avoid segfaults on terminals with only one line, * catch this corner case now and die explicitly. */ if (sp->t_rows == 0) { (void)fprintf(stderr, "Error: Screen too small for visual mode.\n"); return 1; } if (sp->rows != 1) { if (sp->t_rows > sp->rows - 1) { sp->t_minrows = sp->t_rows = sp->rows - 1; msgq(sp, M_INFO, "Windows option value is too large, max is %u", sp->t_rows); } sp->t_maxrows = sp->rows - 1; } else sp->t_maxrows = 1; sp->woff = 0; /* Create a screen map. */ CALLOC_RET(sp, HMAP, SIZE_HMAP(sp), sizeof(SMAP)); TMAP = HMAP + (sp->t_rows - 1); HMAP->lno = sp->lno; HMAP->coff = 0; HMAP->soff = 1; /* * Fill the screen map from scratch -- try and center the line. That * way if we're starting with a file we've seen before, we'll put the * line in the middle, otherwise, it won't work and we'll end up with * the line at the top. */ F_CLR(sp, SC_SCR_TOP); F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER); /* Invalidate the cursor. */ F_SET(vip, VIP_CUR_INVALID); /* Paint the screen image from scratch. */ F_SET(vip, VIP_N_EX_PAINT); return (0); } /* * v_dtoh -- * Move all but the current screen to the hidden queue. */ static void v_dtoh(SCR *sp) { GS *gp; SCR *tsp; int hidden; /* Move all screens to the hidden queue, tossing screen maps. */ hidden = 0; gp = sp->gp; while ((tsp = TAILQ_FIRST(&gp->dq))) { free(_HMAP(tsp)); _HMAP(tsp) = NULL; TAILQ_REMOVE(&gp->dq, tsp, q); TAILQ_INSERT_TAIL(&gp->hq, tsp, q); ++hidden; } /* Move current screen back to the display queue. */ TAILQ_REMOVE(&gp->hq, sp, q); TAILQ_INSERT_TAIL(&gp->dq, sp, q); if (hidden > 2) msgq(sp, M_INFO, "%d screens backgrounded; use `:di s' to display details", hidden - 1); if (hidden == 2) msgq(sp, M_INFO, "Screen backgrounded; use `:di s' to display details"); } /* * v_keyword -- * Get the word (or non-word) the cursor is on. */ static int v_keyword(SCR *sp) { VI_PRIVATE *vip; size_t beg, end, len; int moved, state; char *p; if (db_get(sp, sp->lno, DBG_FATAL, &p, &len)) return (1); /* * !!! * Historically, tag commands skipped over any leading whitespace * characters. Make this true in general when using cursor words. * If movement, getting a cursor word implies moving the cursor to * its beginning. Refresh now. * * !!! * Find the beginning/end of the keyword. Keywords are currently * used for cursor-word searching and for tags. Historical vi * only used the word in a tag search from the cursor to the end * of the word, i.e. if the cursor was on the 'b' in " abc ", the * tag was "bc". For consistency, we make cursor word searches * follow the same rule. */ for (moved = 0, beg = sp->cno; beg < len && isspace(p[beg]); moved = 1, ++beg); if (beg >= len) { msgq(sp, M_BERR, "Cursor not in a word"); return (1); } if (moved) { sp->cno = beg; (void)vs_refresh(sp, 0); } /* Find the end of the word. */ for (state = inword(p[beg]), end = beg; ++end < len && state == inword(p[end]);); vip = VIP(sp); len = (end - beg); BINC_RET(sp, vip->keyw, vip->klen, len); memmove(vip->keyw, p + beg, len); vip->keyw[len] = '\0'; /* XXX */ return (0); } /* * v_alias -- * Check for a command alias. */ static VIKEYS const * v_alias(SCR *sp, VICMD *vp, VIKEYS const *kp) { CHAR_T push; switch (vp->key) { case 'C': /* C -> c$ */ push = '$'; vp->key = 'c'; break; case 'D': /* D -> d$ */ push = '$'; vp->key = 'd'; break; case 'S': /* S -> c_ */ push = '_'; vp->key = 'c'; break; case 'Y': /* Y -> y_ */ push = '_'; vp->key = 'y'; break; default: return (kp); } return (v_event_push(sp, NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]); } /* * v_count -- * Return the next count. */ static int v_count(SCR *sp, CHAR_T fkey, unsigned long *countp) { EVENT ev; unsigned long count, tc; ev.e_c = fkey; count = tc = 0; (void)tc; do { /* * XXX * Assume that overflow results in a smaller number. */ tc = count * 10 + ev.e_c - '0'; if (count > tc) { /* Toss to the next non-digit. */ do { if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) return (1); } while (isdigit(ev.e_c)); msgq(sp, M_ERR, "Number larger than %lu", ULONG_MAX); return (1); } count = tc; if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK) return (1); } while (isdigit(ev.e_c)); *countp = count; return (0); } /* * v_key -- * Return the next event. */ static gcret_t v_key(SCR *sp, int command_events, EVENT *evp, u_int32_t ec_flags) { u_int32_t quote; for (quote = 0;;) { if (v_event_get(sp, evp, 0, ec_flags | quote)) return (GC_FATAL); quote = 0; switch (evp->e_event) { case E_CHARACTER: /* * !!! * Historically, ^V was ignored in the command stream, * although it had a useful side-effect of interrupting * mappings. Adding a quoting bit to the call probably * extends historic practice, but it feels right. */ if (evp->e_value == K_VLNEXT) { quote = EC_QUOTED; break; } return (GC_OK); case E_ERR: case E_EOF: return (GC_FATAL); case E_INTERRUPT: /* * !!! * Historically, vi beeped on command level interrupts. * * Historically, vi exited to ex mode if no file was * named on the command line, and two interrupts were * generated in a row. (Just figured you might want * to know that.) */ (void)sp->gp->scr_bell(sp); return (GC_INTERRUPT); case E_REPAINT: if (vs_repaint(sp, evp)) return (GC_FATAL); break; case E_WRESIZE: return (GC_ERR); case E_QUIT: case E_WRITE: if (command_events) return (GC_EVENT); /* FALLTHROUGH */ default: v_event_err(sp, evp); return (GC_ERR); } } /* NOTREACHED */ } #if defined(DEBUG) && defined(COMLOG) /* * v_comlog -- * Log the contents of the command structure. */ static void v_comlog(SCR *sp, VICMD *vp) { TRACE(sp, "vcmd: %c", vp->key); if (F_ISSET(vp, VC_BUFFER)) TRACE(sp, " buffer: %c", vp->buffer); if (F_ISSET(vp, VC_C1SET)) TRACE(sp, " c1: %lu", vp->count); if (F_ISSET(vp, VC_C2SET)) TRACE(sp, " c2: %lu", vp->count2); TRACE(sp, " flags: 0x%x\n", vp->flags); } #endif /* defined(DEBUG) && defined(COMLOG) */ ================================================ FILE: vi/vi.h ================================================ /* $OpenBSD: vi.h,v 1.12 2022/12/26 19:16:04 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. * * @(#)vi.h 10.19 (Berkeley) 6/30/96 */ #include "../include/compat.h" /* Definition of a vi "word". */ #define inword(ch) (isalnum(ch) || (ch) == '_') typedef struct _vikeys VIKEYS; /* Structure passed around to functions implementing vi commands. */ typedef struct _vicmd { CHAR_T key; /* Command key. */ CHAR_T buffer; /* Buffer. */ CHAR_T character; /* Character. */ unsigned long count; /* Count. */ unsigned long count2; /* Second count (only used by z). */ EVENT ev; /* Associated event. */ #define ISCMD(p, key) ((p) == &vikeys[(key)]) VIKEYS const *kp; /* Command/Motion VIKEYS entry. */ #define ISMOTION(vp) ((vp)->rkp != NULL && F_ISSET((vp)->rkp, V_MOTION)) VIKEYS const *rkp; /* Related C/M VIKEYS entry. */ /* * Historic vi allowed "dl" when the cursor was on the last column, * deleting the last character, and similarly allowed "dw" when * the cursor was on the last column of the file. It didn't allow * "dh" when the cursor was on column 1, although these cases are * not strictly analogous. The point is that some movements would * succeed if they were associated with a motion command, and fail * otherwise. This is part of the off-by-1 schizophrenia that * plagued vi. Other examples are that "dfb" deleted everything * up to and including the next 'b' character, while "d/b" deleted * everything up to the next 'b' character. While this implementation * regularizes the interface to the extent possible, there are many * special cases that can't be fixed. The special cases are handled * by setting flags per command so that the underlying command and * motion routines know what's really going on. * * The VM_* flags are set in the vikeys array and by the underlying * functions (motion component or command) as well. For this reason, * the flags in the VICMD and VIKEYS structures live in the same name * space. */ #define VM_CMDFAILED 0x00000001 /* Command failed. */ #define VM_CUTREQ 0x00000002 /* Always cut into numeric buffers. */ #define VM_LDOUBLE 0x00000004 /* Doubled command for line mode. */ #define VM_LMODE 0x00000008 /* Motion is line oriented. */ #define VM_COMMASK 0x0000000f /* Mask for VM flags. */ /* * The VM_RCM_* flags are single usage, i.e. if you set one, you have * to clear the others. */ #define VM_RCM 0x00000010 /* Use relative cursor movement (RCM). */ #define VM_RCM_SET 0x00000020 /* RCM: set to current position. */ #define VM_RCM_SETFNB 0x00000040 /* RCM: set to first non-blank (FNB). */ #define VM_RCM_SETLAST 0x00000080 /* RCM: set to last character. */ #define VM_RCM_SETNNB 0x00000100 /* RCM: set to next non-blank. */ #define VM_RCM_MASK 0x000001f0 /* Mask for RCM flags. */ /* Flags for the underlying function. */ #define VC_BUFFER 0x00000200 /* The buffer was set. */ #define VC_C1RESET 0x00000400 /* Reset C1SET flag for dot commands. */ #define VC_C1SET 0x00000800 /* Count 1 was set. */ #define VC_C2SET 0x00001000 /* Count 2 was set. */ #define VC_ISDOT 0x00002000 /* Command was the dot command. */ u_int32_t flags; /* * There are four cursor locations that we worry about: the initial * cursor position, the start of the range, the end of the range, * and the final cursor position. The initial cursor position and * the start of the range are both m_start, and are always the same. * All locations are initialized to the starting cursor position by * the main vi routines, and the underlying functions depend on this. * * Commands that can be motion components set the end of the range * cursor position, m_stop. All commands must set the ending cursor * position, m_final. The reason that m_stop isn't the same as m_final * is that there are situations where the final position of the cursor * is outside of the cut/delete range (e.g. 'd[[' from the first column * of a line). The final cursor position often varies based on the * direction of the movement, as well as the command. The only special * case that the delete code handles is that it will make adjustments * if the final cursor position is deleted. * * The reason for all of this is that the historic vi semantics were * defined command-by-command. Every function has to roll its own * starting and stopping positions, and adjust them if it's being used * as a motion component. The general rules are as follows: * * 1: If not a motion component, the final cursor is at the end * of the range. * 2: If moving backward in the file, delete and yank move the * final cursor to the end of the range. * 3: If moving forward in the file, delete and yank leave the * final cursor at the start of the range. * * Usually, if moving backward in the file and it's a motion component, * the starting cursor is decremented by a single character (or, in a * few cases, to the end of the previous line) so that the starting * cursor character isn't cut or deleted. No cursor adjustment is * needed for moving forward, because the cut/delete routines handle * m_stop inclusively, i.e. the last character in the range is cut or * deleted. This makes cutting to the EOF/EOL reasonable. * * The 'c', '<', '>', and '!' commands are special cases. We ignore * the final cursor position for all of them: for 'c', the text input * routines set the cursor to the last character inserted; for '<', * '>' and '!', the underlying ex commands that do the operation will * set the cursor for us, usually to something related to the first * . */ MARK m_start; /* mark: initial cursor, range start. */ MARK m_stop; /* mark: range end. */ MARK m_final; /* mark: final cursor position. */ } VICMD; /* Vi command table structure. */ struct _vikeys { /* Underlying function. */ int (*func)(SCR *, VICMD *); #define V_ABS 0x00004000 /* Absolute movement, set '' mark. */ #define V_ABS_C 0x00008000 /* V_ABS: if the line/column changed. */ #define V_ABS_L 0x00010000 /* V_ABS: if the line changed. */ #define V_CHAR 0x00020000 /* Character (required, trailing). */ #define V_CNT 0x00040000 /* Count (optional, leading). */ #define V_DOT 0x00080000 /* On success, sets dot command. */ #define V_KEYW 0x00100000 /* Cursor referenced word. */ #define V_MOTION 0x00200000 /* Motion (required, trailing). */ #define V_MOVE 0x00400000 /* Command defines movement. */ #define V_OBUF 0x00800000 /* Buffer (optional, leading). */ #define V_RBUF 0x01000000 /* Buffer (required, trailing). */ #define V_SECURE 0x02000000 /* Permission denied if O_SECURE set. */ u_int32_t flags; char *usage; /* Usage line. */ char *help; /* Help line. */ }; #define MAXVIKEY 126 /* List of vi commands. */ extern VIKEYS const vikeys[MAXVIKEY + 1]; extern VIKEYS const tmotion; /* XXX Hacked ~ command. */ /* Character stream structure, prototypes. */ typedef struct _vcs { recno_t cs_lno; /* Line. */ size_t cs_cno; /* Column. */ CHAR_T *cs_bp; /* Buffer. */ size_t cs_len; /* Length. */ CHAR_T cs_ch; /* Character. */ #define CS_EMP 1 /* Empty line. */ #define CS_EOF 2 /* End-of-file. */ #define CS_EOL 3 /* End-of-line. */ #define CS_SOF 4 /* Start-of-file. */ int cs_flags; /* Return flags. */ } VCS; int cs_bblank(SCR *, VCS *); int cs_fblank(SCR *, VCS *); int cs_fspace(SCR *, VCS *); int cs_init(SCR *, VCS *); int cs_next(SCR *, VCS *); int cs_prev(SCR *, VCS *); /* * We use a single "window" for each set of vi screens. The model would be * simpler with two windows (one for the text, and one for the modeline) * because scrolling the text window down would work correctly then, not * affecting the mode line. As it is we have to play games to make it look * right. The reason for this choice is that it would be difficult for * curses to optimize the movement, i.e. detect that the downward scroll * isn't going to change the modeline, set the scrolling region on the * terminal and only scroll the first part of the text window. * * Structure for mapping lines to the screen. An SMAP is an array, with one * structure element per screen line, which holds information describing the * physical line which is displayed in the screen line. The first two fields * (lno and off) are all that are necessary to describe a line. The rest of * the information is useful to keep information from being re-calculated. * * The SMAP always has an entry for each line of the physical screen, plus a * slot for the colon command line, so there is room to add any screen into * another one at screen exit. * * Lno is the line number. If doing the historic vi long line folding, off * is the screen offset into the line. For example, the pair 2:1 would be * the first screen of line 2, and 2:2 would be the second. In the case of * long lines, the screen map will tend to be staggered, e.g., 1:1, 1:2, 1:3, * 2:1, 3:1, etc. If doing left-right scrolling, the off field is the screen * column offset into the lines, and can take on any value, as it's adjusted * by the user set value O_SIDESCROLL. */ typedef struct _smap { recno_t lno; /* 1-N: Physical file line number. */ size_t coff; /* 0-N: Column offset in the line. */ size_t soff; /* 1-N: Screen offset in the line. */ /* vs_line() cache information. */ size_t c_sboff; /* 0-N: offset of first character byte. */ size_t c_eboff; /* 0-N: offset of last character byte. */ u_int8_t c_scoff; /* 0-N: offset into the first character. */ u_int8_t c_eclen; /* 1-N: columns from the last character. */ u_int8_t c_ecsize; /* 1-N: size of the last character. */ } SMAP; /* Macros to flush/test cached information. */ #define SMAP_CACHE(smp) ((smp)->c_ecsize != 0) #define SMAP_FLUSH(smp) ((smp)->c_ecsize = 0) /* Character search information. */ typedef enum { CNOTSET, FSEARCH, fSEARCH, TSEARCH, tSEARCH } cdir_t; typedef enum { AB_NOTSET, AB_NOTWORD, AB_INWORD } abb_t; typedef enum { Q_NOTSET, Q_VNEXT, Q_VTHIS } quote_t; /* Vi private, per-screen memory. */ typedef struct _vi_private { VICMD cmd; /* Current command, motion. */ VICMD motion; /* * !!! * The saved command structure can be modified by the underlying * vi functions, see v_Put() and v_put(). */ VICMD sdot; /* Saved dot, motion command. */ VICMD sdotmotion; CHAR_T *keyw; /* Keyword buffer. */ size_t klen; /* Keyword length. */ size_t keywlen; /* Keyword buffer length. */ CHAR_T rlast; /* Last 'r' replacement character. */ e_key_t rvalue; /* Value of last replacement character. */ EVENT *rep; /* Input replay buffer. */ size_t rep_len; /* Input replay buffer length. */ size_t rep_cnt; /* Input replay buffer characters. */ mtype_t mtype; /* Last displayed message type. */ size_t linecount; /* 1-N: Output overwrite count. */ size_t lcontinue; /* 1-N: Output line continue value. */ size_t totalcount; /* 1-N: Output overwrite count. */ /* Busy state. */ int busy_ref; /* Busy reference count. */ int busy_ch; /* Busy character. */ size_t busy_fx; /* Busy character x coordinate. */ size_t busy_oldy; /* Saved y coordinate. */ size_t busy_oldx; /* Saved x coordinate. */ struct timespec busy_ts;/* Busy timer. */ char *ps; /* Paragraph plus section list. */ unsigned long u_ccnt; /* Undo command count. */ CHAR_T lastckey; /* Last search character. */ cdir_t csearchdir; /* Character search direction. */ SMAP *h_smap; /* First slot of the line map. */ SMAP *t_smap; /* Last slot of the line map. */ /* * One extra slot is always allocated for the map so that we can use * it to do vi :colon command input; see v_tcmd(). */ recno_t sv_tm_lno; /* tcmd: saved TMAP lno field. */ size_t sv_tm_coff; /* tcmd: saved TMAP coff field. */ size_t sv_tm_soff; /* tcmd: saved TMAP soff field. */ size_t sv_t_maxrows; /* tcmd: saved t_maxrows. */ size_t sv_t_minrows; /* tcmd: saved t_minrows. */ size_t sv_t_rows; /* tcmd: saved t_rows. */ #define SIZE_HMAP(sp) (VIP(sp)->srows + 1) /* * Macros to get to the head/tail of the smap. If the screen only has * one line, HMAP can be equal to TMAP, so the code has to understand * the off-by-one errors that can result. If stepping through an SMAP * and operating on each entry, use sp->t_rows as the count of slots, * don't use a loop that compares <= TMAP. */ #define _HMAP(sp) (VIP(sp)->h_smap) #define HMAP _HMAP(sp) #define _TMAP(sp) (VIP(sp)->t_smap) #define TMAP _TMAP(sp) recno_t ss_lno; /* 1-N: vi_opt_screens cached line number. */ size_t ss_screens; /* vi_opt_screens cached return value. */ #define VI_SCR_CFLUSH(vip) ((vip)->ss_lno = OOBLNO) size_t srows; /* 1-N: rows in the terminal/window. */ recno_t olno; /* 1-N: old cursor file line. */ size_t ocno; /* 0-N: old file cursor column. */ size_t sc_col; /* 0-N: LOGICAL screen column. */ SMAP *sc_smap; /* SMAP entry where sc_col occurs. */ #define VIP_CUR_INVALID 0x0001 /* Cursor position is unknown. */ #define VIP_DIVIDER 0x0002 /* Divider line was displayed. */ #define VIP_N_EX_PAINT 0x0004 /* Clear and repaint when ex finishes. */ #define VIP_N_EX_REDRAW 0x0008 /* Schedule SC_SCR_REDRAW when ex finishes. */ #define VIP_N_REFRESH 0x0010 /* Repaint (from SMAP) on the next refresh. */ #define VIP_N_RENUMBER 0x0020 /* Renumber screen on the next refresh. */ #define VIP_RCM_LAST 0x0040 /* Cursor drawn to the last column. */ #define VIP_S_MODELINE 0x0080 /* Skip next modeline refresh. */ #define VIP_S_REFRESH 0x0100 /* Skip next refresh. */ u_int16_t flags; } VI_PRIVATE; /* Vi private area. */ #define VIP(sp) ((VI_PRIVATE *)((sp)->vi_private)) #define O_NUMBER_FMT "%6lu " /* O_NUMBER format, length. */ #define O_NUMBER_LENGTH 7 /* Screen columns. */ #define SCREEN_COLS(sp) \ ((O_ISSET((sp), O_NUMBER) ? (sp)->cols - O_NUMBER_LENGTH : (sp)->cols)) /* * LASTLINE is the zero-based, last line in the screen. Note that it is correct * regardless of the changes in the screen to permit text input on the last line * of the screen, or the existence of small screens. */ #define LASTLINE(sp) \ ((sp)->t_maxrows < (sp)->rows ? (sp)->t_maxrows : (sp)->rows - 1) /* * Small screen (see vs_refresh.c, section 6a) and one-line screen test. * Note, both cannot be true for the same screen. */ #define IS_SMALL(sp) ((sp)->t_minrows != (sp)->t_maxrows) #define IS_ONELINE(sp) ((sp)->rows == 1) /* Half text. */ #define HALFTEXT(sp) ((sp)->t_rows == 1 ? 1 : (sp)->t_rows / 2) /* Half text screen. */ #define HALFSCREEN(sp) ((sp)->t_maxrows == 1 ? 1 : (sp)->t_maxrows / 2) /* * Next tab offset. * * !!! * There are problems with how the historical vi handled tabs. For example, * by doing "set ts=3" and building lines that fold, you can get it to step * through tabs as if they were spaces and move inserted characters to new * positions when is entered. I believe that nvi does tabs correctly, * but there are some historical incompatibilities. */ #define TAB_OFF(c) COL_OFF((c), O_VAL(sp, O_TABSTOP)) /* If more than one screen being shown. */ #define IS_SPLIT(sp) (TAILQ_NEXT((sp), q) || TAILQ_PREV((sp), _dqh, q)) /* Screen adjustment operations. */ typedef enum { A_DECREASE, A_INCREASE, A_SET } adj_t; /* Screen position operations. */ typedef enum { P_BOTTOM, P_FILL, P_MIDDLE, P_TOP } pos_t; /* Scrolling operations. */ typedef enum { CNTRL_B, CNTRL_D, CNTRL_E, CNTRL_F, CNTRL_U, CNTRL_Y, Z_CARAT, Z_PLUS } scroll_t; /* Vi common error messages. */ typedef enum { VIM_COMBUF, VIM_EMPTY, VIM_EOF, VIM_EOL, VIM_NOCOM, VIM_NOCOM_B, VIM_USAGE, VIM_WRESIZE } vim_t; #include "vi_extern.h" ================================================ FILE: vi/vs_line.c ================================================ /* $OpenBSD: vs_line.c,v 1.17 2022/12/26 19:16:04 jmc Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * vs_line -- * Update one line on the screen. * * PUBLIC: int vs_line(SCR *, SMAP *, size_t *, size_t *); */ int vs_line(SCR *sp, SMAP *smp, size_t *yp, size_t *xp) { CHAR_T *kp; GS *gp; SMAP *tsmp; size_t chlen = 0, cno_cnt, cols_per_screen, len, nlen; size_t offset_in_char, offset_in_line, oldx, oldy; size_t scno, skip_cols, skip_screens; int ch = 0, dne, is_cached, is_partial, is_tab, no_draw; int list_tab, list_dollar; char *p, *cbp, *ecbp, cbuf[128]; #if defined(DEBUG) && 0 TRACE(sp, "vs_line: row %u: line: %u off: %u\n", smp - HMAP, smp->lno, smp->off); #endif /* if defined(DEBUG) && 0 */ /* * If ex modifies the screen after ex output is already on the screen, * don't touch it -- we'll get scrolling wrong, at best. */ no_draw = 0; if (!F_ISSET(sp, SC_TINPUT_INFO) && VIP(sp)->totalcount > 1) no_draw = 1; if (F_ISSET(sp, SC_SCR_EXWROTE) && smp - HMAP != LASTLINE(sp)) no_draw = 1; /* * Assume that, if the cache entry for the line is filled in, the * line is already on the screen, and all we need to do is return * the cursor position. If the calling routine doesn't need the * cursor position, we can just return. */ is_cached = SMAP_CACHE(smp); if (yp == NULL && (is_cached || no_draw)) return (0); /* * A nasty side effect of this routine is that it returns the screen * position for the "current" character. Not pretty, but this is the * only routine that really knows what's out there. * * Move to the line. This routine can be called by vs_sm_position(), * which uses it to fill in the cache entry so it can figure out what * the real contents of the screen are. Because of this, we have to * return to wherever we started from. */ gp = sp->gp; (void)gp->scr_cursor(sp, &oldy, &oldx); (void)gp->scr_move(sp, smp - HMAP, 0); /* Get the line. */ dne = db_get(sp, smp->lno, 0, &p, &len); /* * Special case if we're printing the info/mode line. Skip printing * the leading number, as well as other minor setup. The only time * this code paints the mode line is when the user is entering text * for a ":" command, so we can put the code here instead of dealing * with the empty line logic below. This is a kludge, but it's pretty * much confined to this module. * * Set the number of columns for this screen. * Set the number of chars or screens to skip until a character is to * be displayed. */ cols_per_screen = sp->cols; if (O_ISSET(sp, O_LEFTRIGHT)) { skip_screens = 0; skip_cols = smp->coff; } else { skip_screens = smp->soff - 1; skip_cols = skip_screens * cols_per_screen; } list_tab = O_ISSET(sp, O_LIST); if (F_ISSET(sp, SC_TINPUT_INFO)) list_dollar = 0; else { list_dollar = list_tab; /* * If O_NUMBER is set, the line doesn't exist and it's line * number 1, i.e., an empty file, display the line number. * * If O_NUMBER is set, the line exists and the first character * on the screen is the first character in the line, display * the line number. * * !!! * If O_NUMBER set, decrement the number of columns in the * first screen. DO NOT CHANGE THIS -- IT'S RIGHT! The * rest of the code expects this to reflect the number of * columns in the first screen, regardless of the number of * columns we're going to skip. */ if (O_ISSET(sp, O_NUMBER)) { cols_per_screen -= O_NUMBER_LENGTH; if ((!dne || smp->lno == 1) && skip_cols == 0) { nlen = snprintf(cbuf, sizeof(cbuf), O_NUMBER_FMT, (unsigned long)smp->lno); if (nlen >= sizeof(cbuf)) nlen = sizeof(cbuf) - 1; (void)gp->scr_addstr(sp, cbuf, nlen); } } } /* * Special case non-existent lines and the first line of an empty * file. In both cases, the cursor position is 0, but corrected * as necessary for the O_NUMBER field, if it was displayed. */ if (dne || len == 0) { /* Fill in the cursor. */ if (yp != NULL && smp->lno == sp->lno) { *yp = smp - HMAP; *xp = sp->cols - cols_per_screen; } /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; /* Set line cache information. */ smp->c_sboff = smp->c_eboff = 0; smp->c_scoff = smp->c_eclen = 0; /* * Lots of special cases for empty lines, but they only apply * if we're displaying the first screen of the line. */ if (skip_cols == 0) { if (dne) { if (smp->lno == 1) { if (list_dollar) { ch = '$'; goto empty; } } else { ch = '~'; goto empty; } } else if (list_dollar) { ch = '$'; empty: (void)gp->scr_addstr(sp, KEY_NAME(sp, ch), KEY_LEN(sp, ch)); } } (void)gp->scr_clrtoeol(sp); (void)gp->scr_move(sp, oldy, oldx); return (0); } /* * If we just wrote this or a previous line, we cached the starting * and ending positions of that line. The way it works is we keep * information about the lines displayed in the SMAP. If we're * painting the screen in the forward direction, this saves us from * reformatting the physical line for every line on the screen. This * wins big on binary files with 10K lines. * * Test for the first screen of the line, then the current screen line, * then the line behind us, then do the hard work. Note, it doesn't * do us any good to have a line in front of us -- it would be really * hard to try and figure out tabs in the reverse direction, i.e. how * many spaces a tab takes up in the reverse direction depends on * what characters preceded it. * * Test for the first screen of the line. */ if (skip_cols == 0) { smp->c_sboff = offset_in_line = 0; smp->c_scoff = offset_in_char = 0; p = &p[offset_in_line]; goto display; } /* Test to see if we've seen this exact line before. */ if (is_cached) { offset_in_line = smp->c_sboff; offset_in_char = smp->c_scoff; p = &p[offset_in_line]; /* Set cols_per_screen to 2nd and later line length. */ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) cols_per_screen = sp->cols; goto display; } /* Test to see if we saw an earlier part of this line before. */ if (smp != HMAP && SMAP_CACHE(tsmp = smp - 1) && tsmp->lno == smp->lno) { if (tsmp->c_eclen != tsmp->c_ecsize) { offset_in_line = tsmp->c_eboff; offset_in_char = tsmp->c_eclen; } else { offset_in_line = tsmp->c_eboff + 1; offset_in_char = 0; } /* Put starting info for this line in the cache. */ smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char; p = &p[offset_in_line]; /* Set cols_per_screen to 2nd and later line length. */ if (O_ISSET(sp, O_LEFTRIGHT) || skip_cols > cols_per_screen) cols_per_screen = sp->cols; goto display; } scno = 0; offset_in_line = 0; offset_in_char = 0; /* Do it the hard way, for leftright scrolling screens. */ if (O_ISSET(sp, O_LEFTRIGHT)) { for (; offset_in_line < len; ++offset_in_line) { chlen = (ch = *(unsigned char *)p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_LEN(sp, ch); if ((scno += chlen) >= skip_cols) break; } /* Set cols_per_screen to 2nd and later line length. */ cols_per_screen = sp->cols; /* Put starting info for this line in the cache. */ if (scno != skip_cols) { smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char = chlen - (scno - skip_cols); --p; } else { smp->c_sboff = ++offset_in_line; smp->c_scoff = 0; } } /* Do it the hard way, for historic line-folding screens. */ else { for (; offset_in_line < len; ++offset_in_line) { chlen = (ch = *(unsigned char *)p++) == '\t' && !list_tab ? TAB_OFF(scno) : KEY_LEN(sp, ch); if ((scno += chlen) < cols_per_screen) continue; scno -= cols_per_screen; /* Set cols_per_screen to 2nd and later line length. */ cols_per_screen = sp->cols; /* * If crossed the last skipped screen boundary, start * displaying the characters. */ if (--skip_screens == 0) break; } /* Put starting info for this line in the cache. */ if (scno != 0) { smp->c_sboff = offset_in_line; smp->c_scoff = offset_in_char = chlen - scno; --p; } else { smp->c_sboff = ++offset_in_line; smp->c_scoff = 0; } } display: /* * Set the number of characters to skip before reaching the cursor * character. Offset by 1 and use 0 as a flag value. Vs_line is * called repeatedly with a valid pointer to a cursor position. * Don't fill anything in unless it's the right line and the right * character, and the right part of the character... */ if (yp == NULL || smp->lno != sp->lno || sp->cno < offset_in_line || offset_in_line + cols_per_screen < sp->cno) { cno_cnt = 0; /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; } else cno_cnt = (sp->cno - offset_in_line) + 1; ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; /* This is the loop that actually displays characters. */ for (is_partial = 0, scno = 0; offset_in_line < len; ++offset_in_line, offset_in_char = 0) { if ((ch = *(unsigned char *)p++) == '\t' && !list_tab) { scno += chlen = TAB_OFF(scno) - offset_in_char; is_tab = 1; } else { scno += chlen = KEY_LEN(sp, ch) - offset_in_char; is_tab = 0; } /* * Only display up to the right-hand column. Set a flag if * the entire character wasn't displayed for use in setting * the cursor. If reached the end of the line, set the cache * info for the screen. Don't worry about there not being * characters to display on the next screen, its lno/off won't * match up in that case. */ if (scno >= cols_per_screen) { if (is_tab == 1) { chlen -= scno - cols_per_screen; smp->c_ecsize = smp->c_eclen = chlen; scno = cols_per_screen; } else { smp->c_ecsize = chlen; chlen -= scno - cols_per_screen; smp->c_eclen = chlen; if (scno > cols_per_screen) is_partial = 1; } smp->c_eboff = offset_in_line; /* Terminate the loop. */ offset_in_line = len; } /* * If the caller wants the cursor value, and this was the * cursor character, set the value. There are two ways to * put the cursor on a character -- if it's normal display * mode, it goes on the last column of the character. If * it's input mode, it goes on the first. In normal mode, * set the cursor only if the entire character was displayed. */ if (cno_cnt && --cno_cnt == 0 && (F_ISSET(sp, SC_TINPUT) || !is_partial)) { *yp = smp - HMAP; if (F_ISSET(sp, SC_TINPUT)) { if (is_partial) *xp = scno - smp->c_ecsize; else *xp = scno - chlen; } else *xp = scno - 1; if (O_ISSET(sp, O_NUMBER) && !F_ISSET(sp, SC_TINPUT_INFO) && skip_cols == 0) *xp += O_NUMBER_LENGTH; /* If the line is on the screen, quit. */ if (is_cached || no_draw) goto ret1; } /* If the line is on the screen, don't display anything. */ if (is_cached) continue; #define FLUSH(gp, sp, cbp, cbuf) do { \ *(cbp) = '\0'; \ (void)(gp)->scr_addstr((sp), (cbuf), (cbp) - (cbuf)); \ (cbp) = (cbuf); \ } while (0) /* * Display the character. We do tab expansion here because * the screen interface doesn't have any way to set the tab * length. Note, it's theoretically possible for chlen to * be larger than cbuf, if the user set a impossibly large * tabstop. */ if (is_tab) while (chlen--) { if (cbp >= ecbp) FLUSH(gp, sp, cbp, cbuf); if (O_ISSET(sp, O_VISIBLETAB)) *cbp++ = '~'; else *cbp++ = ' '; } else { if (cbp + chlen >= ecbp) FLUSH(gp, sp, cbp, cbuf); for (kp = KEY_NAME(sp, ch) + offset_in_char; chlen--;) *cbp++ = *kp++; } } if (scno < cols_per_screen) { /* If we didn't paint the whole line, update the cache. */ smp->c_ecsize = smp->c_eclen = KEY_LEN(sp, ch); smp->c_eboff = len - 1; /* * If not the info/mode line, and O_LIST set, and at the * end of the line, and the line ended on this screen, * add a trailing $. */ if (list_dollar) { ++scno; chlen = KEY_LEN(sp, '$'); if (cbp + chlen >= ecbp) FLUSH(gp, sp, cbp, cbuf); for (kp = KEY_NAME(sp, '$'); chlen--;) *cbp++ = *kp++; } /* If still didn't paint the whole line, clear the rest. */ if (scno < cols_per_screen) (void)gp->scr_clrtoeol(sp); } /* Flush any buffered characters. */ if (cbp > cbuf) FLUSH(gp, sp, cbp, cbuf); ret1: (void)gp->scr_move(sp, oldy, oldx); return (0); } /* * vs_number -- * Repaint the numbers on all the lines. * * PUBLIC: int vs_number(SCR *); */ int vs_number(SCR *sp) { GS *gp; SMAP *smp; size_t len, oldy, oldx; int exist; char nbuf[10]; gp = sp->gp; /* No reason to do anything if we're in input mode on the info line. */ if (F_ISSET(sp, SC_TINPUT_INFO)) return (0); /* * Try and avoid getting the last line in the file, by getting the * line after the last line in the screen -- if it exists, we know * we have to to number all the lines in the screen. Get the one * after the last instead of the last, so that the info line doesn't * fool us. (The problem is that file_lline will lie, and tell us * that the info line is the last line in the file.) If that test * fails, we have to check each line for existence. */ exist = db_exist(sp, TMAP->lno + 1); (void)gp->scr_cursor(sp, &oldy, &oldx); for (smp = HMAP; smp <= TMAP; ++smp) { /* Numbers are only displayed for the first screen line. */ if (O_ISSET(sp, O_LEFTRIGHT)) { if (smp->coff != 0) continue; } else if (smp->soff != 1) continue; /* * The first line of an empty file gets numbered, otherwise * number any existing line. */ if (smp->lno != 1 && !exist && !db_exist(sp, smp->lno)) break; (void)gp->scr_move(sp, smp - HMAP, 0); len = snprintf(nbuf, sizeof(nbuf), O_NUMBER_FMT, (unsigned long)smp->lno); if (len >= sizeof(nbuf)) len = sizeof(nbuf) - 1; (void)gp->scr_addstr(sp, nbuf, len); } (void)gp->scr_move(sp, oldy, oldx); return (0); } ================================================ FILE: vi/vs_msg.c ================================================ /* $OpenBSD: vs_msg.c,v 1.20 2017/04/18 01:45:35 deraadt Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" typedef enum { SCROLL_W, /* User wait. */ SCROLL_W_EX, /* User wait, or enter : to continue. */ SCROLL_W_QUIT /* User wait, or enter q to quit. */ /* * SCROLL_W_QUIT has another semantic * -- only wait if the screen is full */ } sw_t; static void vs_divider(SCR *); static void vs_msgsave(SCR *, mtype_t, char *, size_t); static void vs_output(SCR *, mtype_t, const char *, int); static void vs_scroll(SCR *, int *, sw_t); static void vs_wait(SCR *, int *, sw_t); /* * vs_busy -- * Display, update or clear a busy message. * * This routine is the default editor interface for vi busy messages. It * implements a standard strategy of stealing lines from the bottom of the * vi text screen. Screens using an alternate method of displaying busy * messages, e.g. X11 clock icons, should set their scr_busy function to the * correct function before calling the main editor routine. * * PUBLIC: void vs_busy(SCR *, const char *, busy_t); */ void vs_busy(SCR *sp, const char *msg, busy_t btype) { GS *gp; VI_PRIVATE *vip; static const char flagc[] = "|/-\\"; struct timespec ts, ts_diff; size_t notused; /* Ex doesn't display busy messages. */ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) return; gp = sp->gp; vip = VIP(sp); /* * Most of this routine is to deal with the screen sharing real estate * between the normal edit messages and the busy messages. Logically, * all that's needed is something that puts up a message, periodically * updates it, and then goes away. */ switch (btype) { case BUSY_ON: ++vip->busy_ref; if (vip->totalcount != 0 || vip->busy_ref != 1) break; /* Initialize state for updates. */ vip->busy_ch = 0; (void)clock_gettime(CLOCK_MONOTONIC, &vip->busy_ts); /* Save the current cursor. */ (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx); /* Display the busy message. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_addstr(sp, msg, strlen(msg)); (void)gp->scr_cursor(sp, ¬used, &vip->busy_fx); (void)gp->scr_clrtoeol(sp); (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); break; case BUSY_OFF: if (vip->busy_ref == 0) break; --vip->busy_ref; /* * If the line isn't in use for another purpose, clear it. * Always return to the original position. */ if (vip->totalcount == 0 && vip->busy_ref == 0) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); } (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx); break; case BUSY_UPDATE: if (vip->totalcount != 0 || vip->busy_ref == 0) break; /* Update no more than every 1/8 of a second. */ (void)clock_gettime(CLOCK_MONOTONIC, &ts); ts_diff = ts; ts_diff.tv_sec -= vip->busy_ts.tv_sec; ts_diff.tv_nsec -= vip->busy_ts.tv_nsec; if (ts_diff.tv_nsec < 0) { ts_diff.tv_sec--; ts_diff.tv_nsec += 1000000000; } if ((ts_diff.tv_sec == 0 && ts_diff.tv_nsec < 125000000) || ts_diff.tv_sec < 0) return; vip->busy_ts = ts; /* Display the update. */ if (vip->busy_ch == sizeof(flagc) - 1) vip->busy_ch = 0; (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); (void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1); (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx); break; } (void)gp->scr_refresh(sp, 0); } /* * vs_home -- * Home the cursor to the bottom row, left-most column. * * PUBLIC: void vs_home(SCR *); */ void vs_home(SCR *sp) { (void)sp->gp->scr_move(sp, LASTLINE(sp), 0); (void)sp->gp->scr_refresh(sp, 0); } /* * vs_update -- * Update a command. * * PUBLIC: void vs_update(SCR *, const char *, const char *); */ void vs_update(SCR *sp, const char *m1, const char *m2) { GS *gp; size_t len, mlen, oldx, oldy; gp = sp->gp; /* * This routine displays a message on the bottom line of the screen, * without updating any of the command structures that would keep it * there for any period of time, i.e. it is overwritten immediately. * * It's used by the ex read and ! commands when the user's command is * expanded, and by the ex substitution confirmation prompt. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) { (void)ex_printf(sp, "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : m2); (void)ex_fflush(sp); } /* * Save the cursor position, the substitute-with-confirmation code * will have already set it correctly. */ (void)gp->scr_cursor(sp, &oldy, &oldx); /* Clear the bottom line. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); /* * XXX * Don't let long file names screw up the screen. */ if (m1 != NULL) { mlen = len = strlen(m1); if (len > sp->cols - 2) mlen = len = sp->cols - 2; (void)gp->scr_addstr(sp, m1, mlen); } else len = 0; if (m2 != NULL) { mlen = strlen(m2); if (len + mlen > sp->cols - 2) mlen = (sp->cols - 2) - len; (void)gp->scr_addstr(sp, m2, mlen); } (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_refresh(sp, 0); } /* * vs_msg -- * Display ex output or error messages for the screen. * * This routine is the default editor interface for all ex output, and all ex * and vi error/informational messages. It implements the standard strategy * of stealing lines from the bottom of the vi text screen. Screens using an * alternate method of displaying messages, e.g. dialog boxes, should set their * scr_msg function to the correct function before calling the editor. * * PUBLIC: void vs_msg(SCR *, mtype_t, char *, size_t); */ void vs_msg(SCR *sp, mtype_t mtype, char *line, size_t len) { GS *gp; VI_PRIVATE *vip; size_t maxcols, oldx, oldy, padding; const char *e, *s, *t; gp = sp->gp; vip = VIP(sp); /* * Ring the bell if it's scheduled. * * XXX * Shouldn't we save this, too? */ if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) { if (F_ISSET(sp, SC_SCR_VI)) { F_CLR(gp, G_BELLSCHED); (void)gp->scr_bell(sp); } else F_SET(gp, G_BELLSCHED); } /* * If vi is using the error line for text input, there's no screen * real-estate for the error message. Nothing to do without some * information as to how important the error message is. */ if (F_ISSET(sp, SC_TINPUT_INFO)) return; /* * Ex or ex controlled screen output. * * If output happens during startup, e.g., a .exrc file, we may be * in ex mode but haven't initialized the screen. Initialize here, * and in this case, stay in ex mode. * * If the SC_SCR_EXWROTE bit is set, then we're switching back and * forth between ex and vi, but the screen is trashed and we have * to respect that. Switch to ex mode long enough to put out the * message. * * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to * the screen, so previous opinions are ignored. */ if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) { if (!F_ISSET(sp, SC_SCR_EX)) { if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (sp->gp->scr_screen(sp, SC_EX)) return; } else if (ex_init(sp)) return; } if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 1); (void)printf("%.*s", (int)len, line); if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 0); (void)fflush(stdout); F_CLR(sp, SC_EX_WAIT_NO); if (!F_ISSET(sp, SC_SCR_EX)) (void)sp->gp->scr_screen(sp, SC_VI); return; } /* If the vi screen isn't ready, save the message. */ if (!F_ISSET(sp, SC_SCR_VI)) { (void)vs_msgsave(sp, mtype, line, len); return; } /* Save the cursor position. */ (void)gp->scr_cursor(sp, &oldy, &oldx); /* If it's an ex output message, just write it out. */ if (mtype == M_NONE) { vs_output(sp, mtype, line, len); goto ret; } /* * If it's a vi message, strip the trailing so we can * try and paste messages together. */ if (line[len - 1] == '\n') --len; /* * If a message won't fit on a single line, try to split on a . * If a subsequent message fits on the same line, write a separator * and output it. Otherwise, put out a newline. * * Need up to two padding characters normally; a semi-colon and a * separating space. If only a single line on the screen, add some * more for the trailing continuation message. * * XXX * Assume that periods and semi-colons take up a single column on the * screen. * * XXX * There are almost certainly pathological cases that will break this * code. */ if (IS_ONELINE(sp)) (void)msg_cmsg(sp, CMSG_CONT_S, &padding); else padding = 0; padding += 2; maxcols = sp->cols - 1; if (vip->lcontinue != 0) { if (len + vip->lcontinue + padding > maxcols) vs_output(sp, vip->mtype, ".\n", 2); else { vs_output(sp, vip->mtype, ";", 1); vs_output(sp, M_NONE, " ", 1); } } vip->mtype = mtype; for (s = line;; s = t) { for (; len > 0 && isblank(*s); --len, ++s); if (len == 0) break; if (len + vip->lcontinue > maxcols) { for (e = s + (maxcols - vip->lcontinue); e > s && !isblank(*e); --e); if (e == s) e = t = s + (maxcols - vip->lcontinue); else for (t = e; isblank(e[-1]); --e); } else e = t = s + len; /* * If the message ends in a period, discard it, we want to * gang messages where possible. */ len -= t - s; if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.') --e; vs_output(sp, mtype, s, e - s); if (len != 0) vs_output(sp, M_NONE, "\n", 1); if (INTERRUPTED(sp)) break; } ret: (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_refresh(sp, 0); } /* * vs_output -- * Output the text to the screen. */ static void vs_output(SCR *sp, mtype_t mtype, const char *line, int llen) { CHAR_T *kp; GS *gp; VI_PRIVATE *vip; size_t chlen, notused; int ch, len, tlen; const char *p, *t; char *cbp, *ecbp, cbuf[128]; gp = sp->gp; vip = VIP(sp); for (p = line; llen > 0;) { /* Get the next physical line. */ if ((p = memchr(line, '\n', llen)) == NULL) len = llen; else len = p - line; /* * The max is sp->cols characters, and we may have already * written part of the line. */ if (len + vip->lcontinue > sp->cols) len = sp->cols - vip->lcontinue; /* * If the first line output, do nothing. If the second line * output, draw the divider line. If drew a full screen, we * remove the divider line. If it's a continuation line, move * to the continuation point, else, move the screen up. */ if (vip->lcontinue == 0) { if (!IS_ONELINE(sp)) { if (vip->totalcount == 1) { (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); (void)gp->scr_clrtoeol(sp); (void)vs_divider(sp); F_SET(vip, VIP_DIVIDER); ++vip->totalcount; ++vip->linecount; } if (vip->totalcount == sp->t_maxrows && F_ISSET(vip, VIP_DIVIDER)) { --vip->totalcount; --vip->linecount; F_CLR(vip, VIP_DIVIDER); } } if (vip->totalcount != 0) vs_scroll(sp, NULL, SCROLL_W_QUIT); (void)gp->scr_move(sp, LASTLINE(sp), 0); ++vip->totalcount; ++vip->linecount; if (INTERRUPTED(sp)) break; } else (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue); /* Error messages are in inverse video. */ if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 1); /* Display the line, doing character translation. */ #define FLUSH { \ *cbp = '\0'; \ (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \ cbp = cbuf; \ } ecbp = (cbp = cbuf) + sizeof(cbuf) - 1; for (t = line, tlen = len; tlen--; ++t) { ch = *t; /* * Replace tabs with spaces, there are places in * ex that do column calculations without looking * at -- and all routines that care about * do their own expansions. This catches * in things like tag search strings. */ if (ch == '\t') ch = ' '; chlen = KEY_LEN(sp, ch); if (cbp + chlen >= ecbp) FLUSH; for (kp = KEY_NAME(sp, ch); chlen--;) *cbp++ = *kp++; } if (cbp > cbuf) FLUSH; if (mtype == M_ERR) (void)gp->scr_attr(sp, SA_INVERSE, 0); /* Clear the rest of the line. */ (void)gp->scr_clrtoeol(sp); /* If we loop, it's a new line. */ vip->lcontinue = 0; /* Reset for the next line. */ line += len; llen -= len; if (p != NULL) { ++line; --llen; } } /* Set up next continuation line. */ if (p == NULL) gp->scr_cursor(sp, ¬used, &vip->lcontinue); } /* * vs_ex_resolve -- * Deal with ex message output. * * This routine is called when exiting a colon command to resolve any ex * output that may have occurred. * * PUBLIC: int vs_ex_resolve(SCR *, int *); */ int vs_ex_resolve(SCR *sp, int *continuep) { EVENT ev; GS *gp; VI_PRIVATE *vip; sw_t wtype; gp = sp->gp; vip = VIP(sp); *continuep = 0; /* If we ran any ex command, we can't trust the cursor position. */ F_SET(vip, VIP_CUR_INVALID); /* Terminate any partially written message. */ if (vip->lcontinue != 0) { vs_output(sp, vip->mtype, ".", 1); vip->lcontinue = 0; vip->mtype = M_NONE; } /* * If we switched out of the vi screen into ex, switch back while we * figure out what to do with the screen and potentially get another * command to execute. * * If we didn't switch into ex, we're not required to wait, and less * than 2 lines of output, we can continue without waiting for the * wait. * * Note, all other code paths require waiting, so we leave the report * of modified lines until later, so that we won't wait for no other * reason than a threshold number of lines were modified. This means * we display cumulative line modification reports for groups of ex * commands. That seems right to me (well, at least not wrong). */ if (F_ISSET(sp, SC_SCR_EXWROTE)) { if (sp->gp->scr_screen(sp, SC_VI)) return (1); } else if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) { F_CLR(sp, SC_EX_WAIT_NO); return (0); } /* Clear the required wait flag, it's no longer needed. */ F_CLR(sp, SC_EX_WAIT_YES); /* * Wait, unless explicitly told not to wait or the user interrupted * the command. If the user is leaving the screen, for any reason, * they can't continue with further ex commands. */ if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) { wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX; if (F_ISSET(sp, SC_SCR_EXWROTE)) vs_wait(sp, continuep, wtype); else vs_scroll(sp, continuep, wtype); if (*continuep) return (0); } /* If ex wrote on the screen, refresh the screen image. */ if (F_ISSET(sp, SC_SCR_EXWROTE)) F_SET(vip, VIP_N_EX_PAINT); /* * If we're not the bottom of the split screen stack, the screen * image itself is wrong, so redraw everything. */ if (TAILQ_NEXT(sp, q)) F_SET(sp, SC_SCR_REDRAW); /* If ex changed the underlying file, the map itself is wrong. */ if (F_ISSET(vip, VIP_N_EX_REDRAW)) F_SET(sp, SC_SCR_REFORMAT); /* Ex may have switched out of the alternate screen, return. */ (void)gp->scr_attr(sp, SA_ALTERNATE, 1); /* * Whew. We're finally back home, after what feels like years. * Kiss the ground. */ F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO); /* * We may need to repaint some of the screen, e.g.: * * :set * :!ls * * gives us a combination of some lines that are "wrong", and a need * for a full refresh. */ if (vip->totalcount > 1) { /* Set up the redraw of the overwritten lines. */ ev.e_event = E_REPAINT; ev.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; ev.e_tlno = sp->rows; /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; /* Redraw. */ (void)vs_repaint(sp, &ev); } else /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; return (0); } /* * vs_resolve -- * Deal with message output. * * PUBLIC: int vs_resolve(SCR *, SCR *, int); */ int vs_resolve(SCR *sp, SCR *csp, int forcewait) { EVENT ev; GS *gp; MSGS *mp; VI_PRIVATE *vip; size_t oldy, oldx; int redraw; /* * Vs_resolve is called from the main vi loop and the refresh function * to periodically ensure that the user has seen any messages that have * been displayed and that any status lines are correct. The sp screen * is the screen we're checking, usually the current screen. When it's * not, csp is the current screen, used for final cursor positioning. */ gp = sp->gp; vip = VIP(sp); if (csp == NULL) csp = sp; /* Save the cursor position. */ (void)gp->scr_cursor(csp, &oldy, &oldx); /* Ring the bell if it's scheduled. */ if (F_ISSET(gp, G_BELLSCHED)) { F_CLR(gp, G_BELLSCHED); (void)gp->scr_bell(sp); } /* Display new file status line. */ if (F_ISSET(sp, SC_STATUS)) { F_CLR(sp, SC_STATUS); msgq_status(sp, sp->lno, MSTAT_TRUNCATE); } /* Report on line modifications. */ mod_rpt(sp); /* * Flush any saved messages. If the screen isn't ready, refresh * it. (A side-effect of screen refresh is that we can display * messages.) Once this is done, don't trust the cursor. That * extra refresh screwed the pooch. */ if (LIST_FIRST(&gp->msgq) != NULL) { if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1)) return (1); while ((mp = LIST_FIRST(&gp->msgq)) != NULL) { gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); LIST_REMOVE(mp, q); free(mp->buf); free(mp); } F_SET(vip, VIP_CUR_INVALID); } switch (vip->totalcount) { case 0: redraw = 0; break; case 1: /* * If we're switching screens, we have to wait for messages, * regardless. If we don't wait, skip updating the modeline. */ if (forcewait) vs_scroll(sp, NULL, SCROLL_W); else F_SET(vip, VIP_S_MODELINE); redraw = 0; break; default: /* * If >1 message line in use, prompt the user to continue and * repaint overwritten lines. */ vs_scroll(sp, NULL, SCROLL_W); ev.e_event = E_REPAINT; ev.e_flno = vip->totalcount >= sp->rows ? 1 : sp->rows - vip->totalcount; ev.e_tlno = sp->rows; redraw = 1; break; } /* Reset the count of overwriting lines. */ vip->linecount = vip->lcontinue = vip->totalcount = 0; /* Redraw. */ if (redraw) (void)vs_repaint(sp, &ev); /* Restore the cursor position. */ (void)gp->scr_move(csp, oldy, oldx); return (0); } /* * vs_scroll -- * Scroll the screen for output. */ static void vs_scroll(SCR *sp, int *continuep, sw_t wtype) { GS *gp; VI_PRIVATE *vip; gp = sp->gp; vip = VIP(sp); if (!IS_ONELINE(sp)) { /* * Scroll the screen. Instead of scrolling the entire screen, * delete the line above the first line output so preserve the * maximum amount of the screen. */ (void)gp->scr_move(sp, vip->totalcount < sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0); (void)gp->scr_deleteln(sp); /* If there are screens below us, push them back into place. */ if (TAILQ_NEXT(sp, q)) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_insertln(sp); } } if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows) return; vs_wait(sp, continuep, wtype); } /* * vs_wait -- * Prompt the user to continue. */ static void vs_wait(SCR *sp, int *continuep, sw_t wtype) { EVENT ev; VI_PRIVATE *vip; const char *p; GS *gp; size_t len; gp = sp->gp; vip = VIP(sp); (void)gp->scr_move(sp, LASTLINE(sp), 0); if (IS_ONELINE(sp)) p = msg_cmsg(sp, CMSG_CONT_S, &len); else switch (wtype) { case SCROLL_W_QUIT: p = msg_cmsg(sp, CMSG_CONT_Q, &len); break; case SCROLL_W_EX: p = msg_cmsg(sp, CMSG_CONT_EX, &len); break; case SCROLL_W: p = msg_cmsg(sp, CMSG_CONT, &len); break; default: abort(); /* NOTREACHED */ } (void)gp->scr_addstr(sp, p, len); ++vip->totalcount; vip->linecount = 0; (void)gp->scr_clrtoeol(sp); (void)gp->scr_refresh(sp, 0); /* Get a single character from the terminal. */ if (continuep != NULL) *continuep = 0; for (;;) { if (v_event_get(sp, &ev, 0, 0)) return; if (ev.e_event == E_CHARACTER) break; if (ev.e_event == E_INTERRUPT) { ev.e_c = CH_QUIT; F_SET(gp, G_INTERRUPTED); break; } (void)gp->scr_bell(sp); } switch (wtype) { case SCROLL_W_QUIT: if (ev.e_c == CH_QUIT) F_SET(gp, G_INTERRUPTED); break; case SCROLL_W_EX: if (ev.e_c == ':' && continuep != NULL) *continuep = 1; break; case SCROLL_W: break; } } /* * vs_divider -- * Draw a dividing line between the screen and the output. */ static void vs_divider(SCR *sp) { GS *gp; size_t len; #define DIVIDESTR "+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+" len = sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1; gp = sp->gp; (void)gp->scr_attr(sp, SA_INVERSE, 1); (void)gp->scr_addstr(sp, DIVIDESTR, len); (void)gp->scr_attr(sp, SA_INVERSE, 0); } /* * vs_msgsave -- * Save a message for later display. */ static void vs_msgsave(SCR *sp, mtype_t mt, char *p, size_t len) { GS *gp; MSGS *mp_c, *mp_n; /* * We have to handle messages before we have any place to put them. * If there's no screen support yet, allocate a msg structure, copy * in the message, and queue it on the global structure. If we can't * allocate memory here, we're genuinely screwed, dump the message * to stderr in the (probably) vain hope that someone will see it. */ CALLOC_GOTO(sp, mp_n, 1, sizeof(MSGS)); MALLOC_GOTO(sp, mp_n->buf, len); memmove(mp_n->buf, p, len); mp_n->len = len; mp_n->mtype = mt; gp = sp->gp; if ((mp_c = LIST_FIRST(&gp->msgq)) == NULL) { LIST_INSERT_HEAD(&gp->msgq, mp_n, q); } else { for (; LIST_NEXT(mp_c, q) != NULL; mp_c = LIST_NEXT(mp_c, q)); LIST_INSERT_AFTER(mp_c, mp_n, q); } return; alloc_err: free(mp_n); (void)fprintf(stderr, "%.*s\n", (int)len, p); } ================================================ FILE: vi/vs_refresh.c ================================================ /* $OpenBSD: vs_refresh.c,v 1.25 2024/04/25 09:58:17 job Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" #define UPDATE_CURSOR 0x01 /* Update the cursor. */ #define UPDATE_SCREEN 0x02 /* Flush to screen. */ static void vs_modeline(SCR *); static int vs_paint(SCR *, unsigned int); /* * v_repaint -- * Repaint selected lines from the screen. * * PUBLIC: int vs_repaint(SCR *, EVENT *); */ int vs_repaint(SCR *sp, EVENT *evp) { SMAP *smp; for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) { smp = HMAP + evp->e_flno - 1; SMAP_FLUSH(smp); if (vs_line(sp, smp, NULL, NULL)) return (1); } return (0); } /* * vs_refresh -- * Refresh all screens. * * PUBLIC: int vs_refresh(SCR *, int); */ int vs_refresh(SCR *sp, int forcepaint) { GS *gp; SCR *tsp; int need_refresh; unsigned int priv_paint, pub_paint; gp = sp->gp; /* * 1: Refresh the screen. * * If SC_SCR_REDRAW is set in the current screen, repaint everything * that we can find, including status lines. */ if (F_ISSET(sp, SC_SCR_REDRAW)) TAILQ_FOREACH(tsp, &gp->dq, q) if (tsp != sp) F_SET(tsp, SC_SCR_REDRAW | SC_STATUS); /* * 2: Related or dirtied screens, or screens with messages. * * If related screens share a view into a file, they may have been * modified as well. Refresh any screens that aren't exiting that * have paint or dirty bits set. Always update their screens, we * are not likely to get another chance. Finally, if we refresh any * screens other than the current one, the cursor will be trashed. */ pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW; priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH; if (O_ISSET(sp, O_NUMBER)) priv_paint |= VIP_N_RENUMBER; TAILQ_FOREACH(tsp, &gp->dq, q) if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) && (F_ISSET(tsp, pub_paint) || F_ISSET(VIP(tsp), priv_paint))) { (void)vs_paint(tsp, (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ? UPDATE_CURSOR : 0) | UPDATE_SCREEN); F_SET(VIP(sp), VIP_CUR_INVALID); } /* * 3: Refresh the current screen. * * Always refresh the current screen, it may be a cursor movement. * Also, always do it last -- that way, SC_SCR_REDRAW can be set * in the current screen only, and the screen won't flash. */ if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint && F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN))) return (1); /* * 4: Paint any missing status lines. * * XXX * This is fairly evil. Status lines are written using the vi message * mechanism, since we have no idea how long they are. Since we may be * painting screens other than the current one, we don't want to make * the user wait. We depend heavily on there not being any other lines * currently waiting to be displayed and the message truncation code in * the msgq_status routine working. * * And, finally, if we updated any status lines, make sure the cursor * gets back to where it belongs. */ need_refresh = 0; TAILQ_FOREACH(tsp, &gp->dq, q) if (F_ISSET(tsp, SC_STATUS)) { need_refresh = 1; vs_resolve(tsp, sp, 0); } if (need_refresh) (void)gp->scr_refresh(sp, 0); /* * A side-effect of refreshing the screen is that it's now ready * for everything else, i.e. messages. */ F_SET(sp, SC_SCR_VI); return (0); } /* * vs_paint -- * This is the guts of the vi curses screen code. The idea is that * the SCR structure passed in contains the new coordinates of the * screen. What makes this hard is that we don't know how big * characters are, doing input can put the cursor in illegal places, * and we're frantically trying to avoid repainting unless it's * absolutely necessary. If you change this code, you'd better know * what you're doing. It's subtle and quick to anger. */ static int vs_paint(SCR *sp, unsigned int flags) { GS *gp; SMAP *smp, tmp; VI_PRIVATE *vip; recno_t lastline, lcnt; size_t cwtotal, cnt, len, notused, off, y; int ch = 0, didpaint, isempty, leftright_warp; char *p; #define LNO sp->lno /* Current file line. */ #define OLNO vip->olno /* Remembered file line. */ #define CNO sp->cno /* Current file column. */ #define OCNO vip->ocno /* Remembered file column. */ #define SCNO vip->sc_col /* Current screen column. */ gp = sp->gp; vip = VIP(sp); didpaint = leftright_warp = 0; /* * 5: Reformat the lines. * * If the lines themselves have changed (:set list, for example), * fill in the map from scratch. Adjust the screen that's being * displayed if the leftright flag is set. */ if (F_ISSET(sp, SC_SCR_REFORMAT)) { /* Invalidate the line size cache. */ VI_SCR_CFLUSH(vip); /* Toss vs_line() cached information. */ if (F_ISSET(sp, SC_SCR_TOP)) { if (vs_sm_fill(sp, LNO, P_TOP)) return (1); } else if (F_ISSET(sp, SC_SCR_CENTER)) { if (vs_sm_fill(sp, LNO, P_MIDDLE)) return (1); } else { if (LNO == HMAP->lno || LNO == TMAP->lno) { cnt = vs_screens(sp, LNO, &CNO); if (LNO == HMAP->lno && cnt < HMAP->soff) HMAP->soff = cnt; if (LNO == TMAP->lno && cnt > TMAP->soff) TMAP->soff = cnt; } if (vs_sm_fill(sp, OOBLNO, P_TOP)) return (1); } F_SET(sp, SC_SCR_REDRAW); } /* * 6: Line movement. * * Line changes can cause the top line to change as well. As * before, if the movement is large, the screen is repainted. * * 6a: Small screens. * * Users can use the window, w300, w1200 and w9600 options to make * the screen artificially small. The behavior of these options * in the historic vi wasn't all that consistent, and, in fact, it * was never documented how various screen movements affected the * screen size. Generally, one of three things would happen: * 1: The screen would expand in size, showing the line * 2: The screen would scroll, showing the line * 3: The screen would compress to its smallest size and * repaint. * In general, scrolling didn't cause compression (200^D was handled * the same as ^D), movement to a specific line would (:N where N * was 1 line below the screen caused a screen compress), and cursor * movement would scroll if it was 11 lines or less, and compress if * it was more than 11 lines. (And, no, I have no idea where the 11 * comes from.) * * What we do is try and figure out if the line is less than half of * a full screen away. If it is, we expand the screen if there's * room, and then scroll as necessary. The alternative is to compress * and repaint. * * !!! * This code is a special case from beginning to end. Unfortunately, * home modems are still slow enough that it's worth having. * * XXX * If the line a really long one, i.e. part of the line is on the * screen but the column offset is not, we'll end up in the adjust * code, when we should probably have compressed the screen. */ if (IS_SMALL(sp)) { if (LNO < HMAP->lno) { lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows); if (lcnt <= HALFSCREEN(sp)) for (; lcnt && sp->t_rows != sp->t_maxrows; --lcnt, ++sp->t_rows) { ++TMAP; if (vs_sm_1down(sp)) return (1); } else goto small_fill; } else if (LNO > TMAP->lno) { lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows); if (lcnt <= HALFSCREEN(sp)) for (; lcnt && sp->t_rows != sp->t_maxrows; --lcnt, ++sp->t_rows) { if (vs_sm_next(sp, TMAP, TMAP + 1)) return (1); ++TMAP; if (vs_line(sp, TMAP, NULL, NULL)) return (1); } else { small_fill: (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { (void)gp->scr_move(sp, TMAP - HMAP, 0); (void)gp->scr_clrtoeol(sp); } if (vs_sm_fill(sp, LNO, P_FILL)) return (1); F_SET(sp, SC_SCR_REDRAW); goto adjust; } } } /* * 6b: Line down, or current screen. */ if (LNO >= HMAP->lno) { /* Current screen. */ if (LNO <= TMAP->lno) goto adjust; if (F_ISSET(sp, SC_SCR_TOP)) goto top; if (F_ISSET(sp, SC_SCR_CENTER)) goto middle; /* * If less than half a screen above the line, scroll down * until the line is on the screen. */ lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { while (lcnt--) if (vs_sm_1up(sp)) return (1); goto adjust; } goto bottom; } /* * 6c: If not on the current screen, may request center or top. */ if (F_ISSET(sp, SC_SCR_TOP)) goto top; if (F_ISSET(sp, SC_SCR_CENTER)) goto middle; /* * 6d: Line up. */ lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { /* * If less than half a screen below the line, scroll up until * the line is the first line on the screen. Special check so * that if the screen has been emptied, we refill it. */ if (db_exist(sp, HMAP->lno)) { while (lcnt--) if (vs_sm_1down(sp)) return (1); goto adjust; } /* * If less than a half screen from the bottom of the file, * put the last line of the file on the bottom of the screen. */ bottom: if (db_last(sp, &lastline)) return (1); tmp.lno = LNO; tmp.coff = HMAP->coff; tmp.soff = 1; lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows); if (lcnt < HALFTEXT(sp)) { if (vs_sm_fill(sp, lastline, P_BOTTOM)) return (1); F_SET(sp, SC_SCR_REDRAW); goto adjust; } /* It's not close, just put the line in the middle. */ goto middle; } /* * If less than half a screen from the top of the file, put the first * line of the file at the top of the screen. Otherwise, put the line * in the middle of the screen. */ tmp.lno = 1; tmp.coff = HMAP->coff; tmp.soff = 1; lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp)); if (lcnt < HALFTEXT(sp)) { if (vs_sm_fill(sp, 1, P_TOP)) return (1); } else middle: if (vs_sm_fill(sp, LNO, P_MIDDLE)) return (1); if (0) { top: if (vs_sm_fill(sp, LNO, P_TOP)) return (1); } F_SET(sp, SC_SCR_REDRAW); /* * At this point we know part of the line is on the screen. Since * scrolling is done using logical lines, not physical, all of the * line may not be on the screen. While that's not necessarily bad, * if the part the cursor is on isn't there, we're going to lose. * This can be tricky; if the line covers the entire screen, lno * may be the same as both ends of the map, that's why we test BOTH * the top and the bottom of the map. This isn't a problem for * left-right scrolling, the cursor movement code handles the problem. * * There's a performance issue here if editing *really* long lines. * This gets to the right spot by scrolling, and, in a binary, by * scrolling hundreds of lines. If the adjustment looks like it's * going to be a serious problem, refill the screen and repaint. */ adjust: if (!O_ISSET(sp, O_LEFTRIGHT) && (LNO == HMAP->lno || LNO == TMAP->lno)) { cnt = vs_screens(sp, LNO, &CNO); if (LNO == HMAP->lno && cnt < HMAP->soff) { if ((HMAP->soff - cnt) > HALFTEXT(sp)) { HMAP->soff = cnt; vs_sm_fill(sp, OOBLNO, P_TOP); F_SET(sp, SC_SCR_REDRAW); } else while (cnt < HMAP->soff) if (vs_sm_1down(sp)) return (1); } if (LNO == TMAP->lno && cnt > TMAP->soff) { if ((cnt - TMAP->soff) > HALFTEXT(sp)) { TMAP->soff = cnt; vs_sm_fill(sp, OOBLNO, P_BOTTOM); F_SET(sp, SC_SCR_REDRAW); } else while (cnt > TMAP->soff) if (vs_sm_1up(sp)) return (1); } } /* * If the screen needs to be repainted, skip cursor optimization. * However, in the code above we skipped leftright scrolling on * the grounds that the cursor code would handle it. Make sure * the right screen is up. */ if (F_ISSET(sp, SC_SCR_REDRAW)) { if (O_ISSET(sp, O_LEFTRIGHT)) goto slow; goto paint; } /* * 7: Cursor movements (current screen only). */ if (!LF_ISSET(UPDATE_CURSOR)) goto number; /* * Decide cursor position. If the line has changed, the cursor has * moved over a tab, or don't know where the cursor was, reparse the * line. Otherwise, we've just moved over fixed-width characters, * and can calculate the left/right scrolling and cursor movement * without reparsing the line. Note that we don't know which (if any) * of the characters between the old and new cursor positions changed. * * XXX * With some work, it should be possible to handle tabs quickly, at * least in obvious situations, like moving right and encountering * a tab, without reparsing the whole line. * * If the line we're working with has changed, reread it.. */ if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO) goto slow; /* Otherwise, if nothing's changed, ignore the cursor. */ if (CNO == OCNO) goto fast; /* * Get the current line. If this fails, we either have an empty * file and can just repaint, or there's a real problem. This * isn't a performance issue because there aren't any ways to get * here repeatedly. */ if (db_eget(sp, LNO, &p, &len, &isempty)) { if (isempty) goto slow; return (1); } #ifdef DEBUG /* Sanity checking. */ if (CNO >= len && len != 0) { msgq(sp, M_ERR, "Error: %s/%d: cno (%u) >= len (%u)", openbsd_basename(__FILE__), __LINE__, CNO, len); return (1); } #endif /* ifdef DEBUG */ /* * The basic scheme here is to look at the characters in between * the old and new positions and decide how big they are on the * screen, and therefore, how many screen positions to move. */ if (CNO < OCNO) { /* * 7a: Cursor moved left. * * Point to the old character. The old cursor position can * be past EOL if, for example, we just deleted the rest of * the line. In this case, since we don't know the width of * the characters we traversed, we have to do it slowly. */ p += OCNO; cnt = (OCNO - CNO) + 1; if (OCNO >= len) goto slow; /* * Quick sanity check -- it's hard to figure out exactly when * we cross a screen boundary as we do in the cursor right * movement. If cnt is so large that we're going to cross the * boundary no matter what, stop now. */ if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt) goto slow; /* * Count up the widths of the characters. If it's a tab * character, go do it the slow way. */ for (cwtotal = 0; cnt--; cwtotal += KEY_LEN(sp, ch)) if ((ch = *(unsigned char *)p--) == '\t') goto slow; /* * Decrement the screen cursor by the total width of the * characters minus 1. */ cwtotal -= 1; /* * If we're moving left, and there's a wide character in the * current position, go to the end of the character. */ if (KEY_LEN(sp, ch) > 1) cwtotal -= KEY_LEN(sp, ch) - 1; /* * If the new column moved us off of the current logical line, * calculate a new one. If doing leftright scrolling, we've * moved off of the current screen, as well. */ if (SCNO < cwtotal) goto slow; SCNO -= cwtotal; } else { /* * 7b: Cursor moved right. * * Point to the first character to the right. */ p += OCNO + 1; cnt = CNO - OCNO; /* * Count up the widths of the characters. If it's a tab * character, go do it the slow way. If we cross a * screen boundary, we can quit. */ for (cwtotal = SCNO; cnt--;) { if ((ch = *(unsigned char *)p++) == '\t') goto slow; if ((cwtotal += KEY_LEN(sp, ch)) >= SCREEN_COLS(sp)) break; } /* * Increment the screen cursor by the total width of the * characters. */ SCNO = cwtotal; /* See screen change comment in section 6a. */ if (SCNO >= SCREEN_COLS(sp)) goto slow; } /* * 7c: Fast cursor update. * * We have the current column, retrieve the current row. */ fast: (void)gp->scr_cursor(sp, &y, ¬used); goto done_cursor; /* * 7d: Slow cursor update. * * Walk through the map and find the current line. */ slow: for (smp = HMAP; smp->lno != LNO; ++smp); (void)(unsigned int)0; /* * 7e: Leftright scrolling adjustment. * * If doing left-right scrolling and the cursor movement has changed * the displayed screen, scroll the screen left or right, unless we're * updating the info line in which case we just scroll that one line. * We adjust the offset up or down until we have a window that covers * the current column, making sure that we adjust differently for the * first screen as compared to subsequent ones. */ if (O_ISSET(sp, O_LEFTRIGHT)) { /* * Get the screen column for this character, and correct * for the number option offset. */ cnt = vs_columns(sp, NULL, LNO, &CNO, NULL); if (O_ISSET(sp, O_NUMBER) && cnt) cnt -= O_NUMBER_LENGTH; /* Adjust the window towards the beginning of the line. */ off = smp->coff; if (off >= cnt) { do { if (off >= O_VAL(sp, O_SIDESCROLL)) off -= O_VAL(sp, O_SIDESCROLL); else { off = 0; break; } } while (off >= cnt); goto shifted; } /* Adjust the window towards the end of the line. */ if ((off == 0 && off + SCREEN_COLS(sp) < cnt) || (off != 0 && off + sp->cols < cnt)) { do { off += O_VAL(sp, O_SIDESCROLL); } while (off + sp->cols < cnt); shifted: /* Fill in screen map with the new offset. */ if (F_ISSET(sp, SC_TINPUT_INFO)) smp->coff = off; else { for (smp = HMAP; smp <= TMAP; ++smp) smp->coff = off; leftright_warp = 1; } goto paint; } /* * We may have jumped here to adjust a leftright screen because * redraw was set. If so, we have to paint the entire screen. */ if (F_ISSET(sp, SC_SCR_REDRAW)) goto paint; } /* * Update the screen lines for this particular file line until we * have a new screen cursor position. */ for (y = -1, vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) { if (vs_line(sp, smp, &y, &SCNO)) return (1); if (y != -1) { vip->sc_smap = smp; break; } } goto done_cursor; /* * 8: Repaint the entire screen. * * Lost big, do what you have to do. We flush the cache, since * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and * it's simpler to repaint. So, don't trust anything that we * think we know about it. */ paint: for (smp = HMAP; smp <= TMAP; ++smp) SMAP_FLUSH(smp); for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) { if (vs_line(sp, smp, &y, &SCNO)) return (1); if (y != -1 && vip->sc_smap == NULL) vip->sc_smap = smp; } /* * If it's a small screen and we're redrawing, clear the unused lines, * ex may have overwritten them. */ if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp)) for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) { (void)gp->scr_move(sp, cnt, 0); (void)gp->scr_clrtoeol(sp); } didpaint = 1; done_cursor: /* * Sanity checking. When the repainting code messes up, the usual * result is we don't repaint the cursor and so sc_smap will be * NULL. If we're debugging, die, otherwise restart from scratch. */ #ifdef DEBUG if (vip->sc_smap == NULL) abort(); #else if (vip->sc_smap == NULL) { if (F_ISSET(sp, SC_SCR_REFORMAT)) return (0); F_SET(sp, SC_SCR_REFORMAT); return (vs_paint(sp, flags)); } #endif /* ifdef DEBUG */ /* * 9: Set the remembered cursor values. */ OCNO = CNO; OLNO = LNO; /* * 10: Repaint the line numbers. * * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we * didn't repaint the screen, repaint all of the line numbers, * they've changed. */ number: if (O_ISSET(sp, O_NUMBER) && F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp)) return (1); /* * 11: Update the mode line, position the cursor, and flush changes. * * If we warped the screen, we have to refresh everything. */ if (leftright_warp) LF_SET(UPDATE_CURSOR | UPDATE_SCREEN); if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) && !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO)) vs_modeline(sp); if (LF_ISSET(UPDATE_CURSOR)) { (void)gp->scr_move(sp, y, SCNO); /* * XXX * If the screen shifted, we recalculate the "most favorite" * cursor position. Vi won't know that we've warped the * screen, so it's going to have a wrong idea about where the * cursor should be. This is vi's problem, and fixing it here * is a gross layering violation. */ if (leftright_warp) (void)vs_column(sp, &sp->rcm); } if (LF_ISSET(UPDATE_SCREEN)) (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT)); /* 12: Clear the flags that are handled by this routine. */ F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP); F_CLR(vip, VIP_CUR_INVALID | VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE); return (0); #undef LNO #undef OLNO #undef CNO #undef OCNO #undef SCNO } /* * vs_modeline -- * Update the mode line. */ static void vs_modeline(SCR *sp) { static char * const modes[] = { "Append", /* SM_APPEND */ "Change", /* SM_CHANGE */ "Command", /* SM_COMMAND */ "Insert", /* SM_INSERT */ "Replace", /* SM_REPLACE */ }; GS *gp; size_t cols, curcol, curlen, endpoint, len, midpoint; const char *t = NULL; int ellipsis; char *p, buf[30]; recno_t last = 0; /* * It's possible that this routine will be called after sp->frp * has been set to NULL by file_end(). We return immediately * to avoid a SEGV. */ if (sp->frp == NULL) return; len = 0; (void)len; midpoint = 0; (void)midpoint; gp = sp->gp; (void)gp; /* * We put down the file name, the ruler, the mode and the dirty flag. * If there's not enough room, there's not enough room, we don't play * any special games. We try to put the ruler in the middle and the * mode and dirty flag at the end. * * !!! * Leave the last character blank, in case it's a really dumb terminal * with hardware scroll. Second, don't paint the last character in the * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you. * * Move to the last line on the screen. */ (void)gp->scr_move(sp, LASTLINE(sp), 0); /* If windowname is set, or >1 screen exists, then show the name */ curlen = 0; if ((IS_SPLIT(sp)) || O_ISSET(sp, O_WINDOWNAME) || O_ISSET(sp, O_SHOWFILENAME)) { for (p = sp->frp->name; *p != '\0'; ++p); for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) { if (*p == '/') { ++p; break; } if ((curlen += KEY_LEN(sp, *p)) > cols) { ellipsis = 3; curlen += KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' '); while (curlen > cols) { ++p; curlen -= KEY_LEN(sp, *p); } break; } } if (ellipsis) { while (ellipsis--) (void)gp->scr_addstr(sp, KEY_NAME(sp, '.'), KEY_LEN(sp, '.')); (void)gp->scr_addstr(sp, KEY_NAME(sp, ' '), KEY_LEN(sp, ' ')); } for (; *p != '\0'; ++p) (void)gp->scr_addstr(sp, KEY_NAME(sp, *p), KEY_LEN(sp, *p)); } /* Clear the rest of the line. */ (void)gp->scr_clrtoeol(sp); /* * Display the ruler. If we're not at the midpoint yet, move there. * Otherwise, add in two extra spaces. * * Adjust the current column for the fact that the editor uses it as * a zero-based number. * * XXX * Assume that numbers, commas, and spaces only take up a single * column on the screen. */ cols = sp->cols - 1; if (O_ISSET(sp, O_RULER)) { vs_column(sp, &curcol); if (!(db_last(sp, &last))) { if (last > 1) { len = snprintf(buf, sizeof(buf), "%lu:%lu %2lu%%", (unsigned long)sp->lno, (unsigned long)curcol + 1, (unsigned long)((((unsigned long)sp->lno) * 100L) / (unsigned long)last)); if (sp->lno >= last) len = snprintf(buf, sizeof(buf), "%lu:%lu Bot", (unsigned long)sp->lno, (unsigned long)curcol + 1); if (sp->lno < 2) len = snprintf(buf, sizeof(buf), "%lu:%lu Top", (unsigned long)sp->lno, (unsigned long)curcol + 1); } else { len = snprintf(buf, sizeof(buf), "%lu:%lu", (unsigned long)sp->lno, (unsigned long)curcol + 1); } } else { len = snprintf(buf, sizeof(buf), "%lu:%lu", (unsigned long)sp->lno, (unsigned long)curcol + 1); } midpoint = (cols - ((len + 1) / 2)) / 2; if (curlen < midpoint) { (void)gp->scr_move(sp, LASTLINE(sp), midpoint); curlen += len; } else if (curlen + 2 + len < cols) { (void)gp->scr_addstr(sp, " ", 2); curlen += 2 + len; } (void)gp->scr_addstr(sp, buf, len); } /* * Display the mode and the modified flag, as close to the end of the * line as possible, but guaranteeing at least two spaces between the * ruler and the modified flag. */ #define MODESIZE 9 endpoint = cols; if (O_ISSET(sp, O_SHOWMODE)) { if (F_ISSET(sp->ep, F_MODIFIED)) --endpoint; t = modes[sp->showmode]; endpoint -= (len = strlen(t)); } if (endpoint > curlen + 2) { (void)gp->scr_move(sp, LASTLINE(sp), endpoint); if (O_ISSET(sp, O_SHOWMODE)) { if (F_ISSET(sp->ep, F_MODIFIED)) (void)gp->scr_addstr(sp, KEY_NAME(sp, '*'), KEY_LEN(sp, '*')); (void)gp->scr_addstr(sp, t, len); } } } ================================================ FILE: vi/vs_relative.c ================================================ /* $OpenBSD: vs_relative.c,v 1.9 2014/11/12 04:28:41 bentley Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" /* * vs_column -- * Return the logical column of the cursor in the line. * * PUBLIC: int vs_column(SCR *, size_t *); */ int vs_column(SCR *sp, size_t *colp) { VI_PRIVATE *vip; vip = VIP(sp); *colp = (O_ISSET(sp, O_LEFTRIGHT) ? vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) + vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0); return (0); } /* * vs_screens -- * Return the screens necessary to display the line, or if specified, * the physical character column within the line, including space * required for the O_NUMBER and O_LIST options. * * PUBLIC: size_t vs_screens(SCR *, recno_t, size_t *); */ size_t vs_screens(SCR *sp, recno_t lno, size_t *cnop) { size_t cols, screens; /* Left-right screens are simple, it's always 1. */ if (O_ISSET(sp, O_LEFTRIGHT)) return (1); /* * Check for a cached value. We maintain a cache because, if the * line is large, this routine gets called repeatedly. One other * hack, lots of time the cursor is on column one, which is an easy * one. */ if (cnop == NULL) { if (VIP(sp)->ss_lno == lno) return (VIP(sp)->ss_screens); } else if (*cnop == 0) return (1); /* Figure out how many columns the line/column needs. */ cols = vs_columns(sp, NULL, lno, cnop, NULL); screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0)); if (screens == 0) screens = 1; /* Cache the value. */ if (cnop == NULL) { VIP(sp)->ss_lno = lno; VIP(sp)->ss_screens = screens; } return (screens); } /* * vs_columns -- * Return the screen columns necessary to display the line, or, * if specified, the physical character column within the line. * * PUBLIC: size_t vs_columns(SCR *, char *, recno_t, size_t *, size_t *); */ size_t vs_columns(SCR *sp, char *lp, recno_t lno, size_t *cnop, size_t *diffp) { size_t chlen, cno, curoff, last, len, scno; int ch, leftright, listset; char *p; /* * Initialize the screen offset. */ scno = 0; curoff = 0; /* Leading number if O_NUMBER option set. */ if (O_ISSET(sp, O_NUMBER)) { scno += O_NUMBER_LENGTH; curoff += O_NUMBER_LENGTH; } /* Need the line to go any further. */ if (lp == NULL) { (void)db_get(sp, lno, 0, &lp, &len); if (len == 0) goto done; } /* Missing or empty lines are easy. */ if (lp == NULL) { done: if (diffp != NULL) /* XXX */ *diffp = 0; return (scno); } /* Store away the values of the list and leftright edit options. */ listset = O_ISSET(sp, O_LIST); leftright = O_ISSET(sp, O_LEFTRIGHT); /* * Initialize the pointer into the buffer. */ p = lp; curoff = 0; /* Macro to return the display length of any signal character. */ #define CHLEN(val) (ch = *(unsigned char *)p++) == '\t' && \ !listset ? TAB_OFF(val) : KEY_LEN(sp, ch); /* * If folding screens (the historic vi screen format), past the end * of the current screen, and the character was a tab, reset the * current screen column to 0, and the total screen columns to the * last column of the screen. Otherwise, display the rest of the * character in the next screen. */ #define TAB_RESET { \ curoff += chlen; \ if (!leftright && curoff >= sp->cols) { \ if (ch == '\t') { \ curoff = 0; \ scno -= scno % sp->cols; \ } else \ curoff -= sp->cols; \ } \ } if (cnop == NULL) while (len--) { chlen = CHLEN(curoff); last = scno; scno += chlen; TAB_RESET; } else for (cno = *cnop;; --cno) { chlen = CHLEN(curoff); last = scno; scno += chlen; TAB_RESET; if (cno == 0) break; } /* Add the trailing '$' if the O_LIST option set. */ if (listset && cnop == NULL) scno += KEY_LEN(sp, '$'); /* * The text input screen code needs to know how much additional * room the last two characters required, so that it can handle * tab character displays correctly. */ if (diffp != NULL) *diffp = scno - last; return (scno); } /* * vs_rcm -- * Return the physical column from the line that will display a * character closest to the currently most attractive character * position (which is stored as a screen column). * * PUBLIC: size_t vs_rcm(SCR *, recno_t, int); */ size_t vs_rcm(SCR *sp, recno_t lno, int islast) { size_t len; /* Last character is easy, and common. */ if (islast) { if (db_get(sp, lno, 0, NULL, &len) || len == 0) return (0); return (len - 1); } /* First character is easy, and common. */ if (sp->rcm == 0) return (0); return (vs_colpos(sp, lno, sp->rcm)); } /* * vs_colpos -- * Return the physical column from the line that will display a * character closest to the specified screen column. * * PUBLIC: size_t vs_colpos(SCR *, recno_t, size_t); */ size_t vs_colpos(SCR *sp, recno_t lno, size_t cno) { size_t chlen, curoff, len, llen, off, scno; int ch, leftright, listset; char *lp, *p; /* Need the line to go any further. */ (void)db_get(sp, lno, 0, &lp, &llen); /* Missing or empty lines are easy. */ if (lp == NULL || llen == 0) return (0); /* Store away the values of the list and leftright edit options. */ listset = O_ISSET(sp, O_LIST); leftright = O_ISSET(sp, O_LEFTRIGHT); /* Discard screen (logical) lines. */ off = cno / sp->cols; cno %= sp->cols; for (scno = 0, p = lp, len = llen; off--;) { for (; len && scno < sp->cols; --len) scno += CHLEN(scno); /* * If reached the end of the physical line, return the last * physical character in the line. */ if (len == 0) return (llen - 1); /* * If folding screens (the historic vi screen format), past * the end of the current screen, and the character was a tab, * reset the current screen column to 0. Otherwise, the rest * of the character is displayed in the next screen. */ if (leftright && ch == '\t') scno = 0; else scno -= sp->cols; } /* Step through the line until reach the right character or EOL. */ for (curoff = scno; len--;) { chlen = CHLEN(curoff); /* * If we've reached the specific character, there are three * cases. * * 1: scno == cno, i.e. the current character ends at the * screen character we care about. * a: off < llen - 1, i.e. not the last character in * the line, return the offset of the next character. * b: else return the offset of the last character. * 2: scno != cno, i.e. this character overruns the character * we care about, return the offset of this character. */ if ((scno += chlen) >= cno) { off = p - lp; return (scno == cno ? (off < llen - 1 ? off : llen - 1) : off - 1); } TAB_RESET; } /* No such character; return the start of the last character. */ return (llen - 1); } ================================================ FILE: vi/vs_smap.c ================================================ /* $OpenBSD: vs_smap.c,v 1.9 2016/01/06 22:28:52 millert Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static int vs_deleteln(SCR *, int); static int vs_insertln(SCR *, int); static int vs_sm_delete(SCR *, recno_t); static int vs_sm_down(SCR *, MARK *, recno_t, scroll_t, SMAP *); static int vs_sm_erase(SCR *); static int vs_sm_insert(SCR *, recno_t); static int vs_sm_reset(SCR *, recno_t); static int vs_sm_up(SCR *, MARK *, recno_t, scroll_t, SMAP *); /* * vs_change -- * Make a change to the screen. * * PUBLIC: int vs_change(SCR *, recno_t, lnop_t); */ int vs_change(SCR *sp, recno_t lno, lnop_t op) { VI_PRIVATE *vip; SMAP *p; size_t cnt, oldy, oldx; vip = VIP(sp); /* * XXX * Very nasty special case. The historic vi code displays a single * space (or a '$' if the list option is set) for the first line in * an "empty" file. If we "insert" a line, that line gets scrolled * down, not repainted, so it's incorrect when we refresh the screen. * The vi text input functions detect it explicitly and don't insert * a new line. * * Check for line #2 before going to the end of the file. */ if (((op == LINE_APPEND && lno == 0) || (op == LINE_INSERT && lno == 1)) && !db_exist(sp, 2)) { lno = 1; op = LINE_RESET; } /* Appending is the same as inserting, if the line is incremented. */ if (op == LINE_APPEND) { ++lno; op = LINE_INSERT; } /* Ignore the change if the line is after the map. */ if (lno > TMAP->lno) return (0); /* * If the line is before the map, and it's a decrement, decrement * the map. If it's an increment, increment the map. Otherwise, * ignore it. */ if (lno < HMAP->lno) { switch (op) { case LINE_APPEND: abort(); /* NOTREACHED */ case LINE_DELETE: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) --p->lno; if (sp->lno >= lno) --sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) ++p->lno; if (sp->lno >= lno) ++sp->lno; F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: break; } return (0); } F_SET(vip, VIP_N_REFRESH); /* * Invalidate the line size cache, and invalidate the cursor if it's * on this line, */ VI_SCR_CFLUSH(vip); if (sp->lno == lno) F_SET(vip, VIP_CUR_INVALID); /* * If ex modifies the screen after ex output is already on the screen * or if we've switched into ex canonical mode, don't touch it -- we'll * get scrolling wrong, at best. */ if (!F_ISSET(sp, SC_TINPUT_INFO) && (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) { F_SET(vip, VIP_N_EX_REDRAW); return (0); } /* Save and restore the cursor for these routines. */ (void)sp->gp->scr_cursor(sp, &oldy, &oldx); switch (op) { case LINE_DELETE: if (vs_sm_delete(sp, lno)) return (1); F_SET(vip, VIP_N_RENUMBER); break; case LINE_INSERT: if (vs_sm_insert(sp, lno)) return (1); F_SET(vip, VIP_N_RENUMBER); break; case LINE_RESET: if (vs_sm_reset(sp, lno)) return (1); break; default: abort(); } (void)sp->gp->scr_move(sp, oldy, oldx); return (0); } /* * vs_sm_fill -- * Fill in the screen map, placing the specified line at the * right position. There isn't any way to tell if an SMAP * entry has been filled in, so this routine had better be * called with P_FILL set before anything else is done. * * !!! * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP * slot is already filled in, P_BOTTOM means that the TMAP slot is * already filled in, and we just finish up the job. * * PUBLIC: int vs_sm_fill(SCR *, recno_t, pos_t); */ int vs_sm_fill(SCR *sp, recno_t lno, pos_t pos) { SMAP *p, tmp; size_t cnt; /* Flush all cached information from the SMAP. */ for (p = HMAP, cnt = sp->t_rows; cnt--; ++p) SMAP_FLUSH(p); /* * If the map is filled, the screen must be redrawn. * * XXX * This is a bug. We should try and figure out if the desired line * is already in the map or close by -- scrolling the screen would * be a lot better than redrawing. */ F_SET(sp, SC_SCR_REDRAW); switch (pos) { case P_FILL: tmp.lno = 1; tmp.coff = 0; tmp.soff = 1; /* See if less than half a screen from the top. */ if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { lno = 1; goto top; } /* See if less than half a screen from the bottom. */ if (db_last(sp, &tmp.lno)) return (1); tmp.coff = 0; tmp.soff = vs_screens(sp, tmp.lno, NULL); if (vs_sm_nlines(sp, &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) { TMAP->lno = tmp.lno; TMAP->coff = tmp.coff; TMAP->soff = tmp.soff; goto bottom; } goto middle; case P_TOP: if (lno != OOBLNO) { top: HMAP->lno = lno; HMAP->coff = 0; HMAP->soff = 1; } else { /* * If number of lines HMAP->lno (top line) spans * changed due to, say reformatting, and now is * fewer than HMAP->soff, reset so the line is * redrawn at the top of the screen. */ cnt = vs_screens(sp, HMAP->lno, NULL); if (cnt < HMAP->soff) HMAP->soff = 1; } /* If we fail, just punt. */ for (p = HMAP, cnt = sp->t_rows; --cnt; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_MIDDLE: /* If we fail, guess that the file is too small. */ middle: p = HMAP + sp->t_rows / 2; p->lno = lno; p->coff = 0; p->soff = 1; for (; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } /* If we fail, just punt. */ p = HMAP + sp->t_rows / 2; for (; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) goto err; break; case P_BOTTOM: if (lno != OOBLNO) { TMAP->lno = lno; TMAP->coff = 0; TMAP->soff = vs_screens(sp, lno, NULL); } /* If we fail, guess that the file is too small. */ bottom: for (p = TMAP; p > HMAP; --p) if (vs_sm_prev(sp, p, p - 1)) { lno = 1; goto top; } break; default: abort(); } return (0); /* * Try and put *something* on the screen. If this fails, we have a * serious hard error. */ err: HMAP->lno = 1; HMAP->coff = 0; HMAP->soff = 1; for (p = HMAP; p < TMAP; ++p) if (vs_sm_next(sp, p, p + 1)) return (1); return (0); } /* * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the * screen contains only a single line (whether because the screen is small * or the line large), it gets fairly exciting. Skip the fun, set a flag * so the screen map is refilled and the screen redrawn, and return. This * is amazingly slow, but it's not clear that anyone will care. */ #define HANDLE_WEIRDNESS(cnt) { \ if ((cnt) >= sp->t_rows) { \ F_SET(sp, SC_SCR_REFORMAT); \ return (0); \ } \ } /* * vs_sm_delete -- * Delete a line out of the SMAP. */ static int vs_sm_delete(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig; /* * Find the line in the map, and count the number of screen lines * which display any part of the deleted line. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) cnt_orig = 1; else for (cnt_orig = 1, t = p + 1; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); HANDLE_WEIRDNESS(cnt_orig); /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, cnt_orig)) return (1); /* Shift the screen map up. */ memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Decrement the line numbers for the rest of the map. */ for (t = TMAP - cnt_orig; p <= t; ++p) --p->lno; /* Display the new lines. */ for (p = TMAP - cnt_orig;;) { if (p < TMAP && vs_sm_next(sp, p, p + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++p, NULL, NULL)) return (1); if (p == TMAP) break; } return (0); } /* * vs_sm_insert -- * Insert a line into the SMAP. */ static int vs_sm_insert(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig, cnt, coff; /* Save the offset. */ coff = HMAP->coff; /* * Find the line in the map, find out how many screen lines * needed to display the line. */ for (p = HMAP; p->lno != lno; ++p); cnt_orig = vs_screens(sp, lno, NULL); HANDLE_WEIRDNESS(cnt_orig); /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (cnt_orig > cnt) cnt_orig = cnt; /* Push down that many lines. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, cnt_orig)) return (1); /* Shift the screen map down. */ memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP)); /* Increment the line numbers for the rest of the map. */ for (t = p + cnt_orig; t <= TMAP; ++t) ++t->lno; /* Fill in the SMAP for the new lines, and display. */ for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) { t->lno = lno; t->coff = coff; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } return (0); } /* * vs_sm_reset -- * Reset a line in the SMAP. */ static int vs_sm_reset(SCR *sp, recno_t lno) { SMAP *p, *t; size_t cnt_orig, cnt_new, cnt, diff; /* * See if the number of on-screen rows taken up by the old display * for the line is the same as the number needed for the new one. * If so, repaint, otherwise do it the hard way. */ for (p = HMAP; p->lno != lno; ++p); if (O_ISSET(sp, O_LEFTRIGHT)) { t = p; cnt_orig = cnt_new = 1; } else { for (cnt_orig = 0, t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t); cnt_new = vs_screens(sp, lno, NULL); } HANDLE_WEIRDNESS(cnt_orig); if (cnt_orig == cnt_new) { do { SMAP_FLUSH(p); if (vs_line(sp, p, NULL, NULL)) return (1); } while (++p < t); return (0); } if (cnt_orig < cnt_new) { /* Get the difference. */ diff = cnt_new - cnt_orig; /* * The lines left in the screen override the number of screen * lines in the inserted line. */ cnt = (TMAP - p) + 1; if (diff > cnt) diff = cnt; /* If there are any following lines, push them down. */ if (cnt > 1) { (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_insertln(sp, diff)) return (1); /* Shift the screen map down. */ memmove(p + diff, p, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); } /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } } else { /* Get the difference. */ diff = cnt_orig - cnt_new; /* Delete that many lines from the screen. */ (void)sp->gp->scr_move(sp, p - HMAP, 0); if (vs_deleteln(sp, diff)) return (1); /* Shift the screen map up. */ memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP)); /* Fill in the SMAP for the replaced line, and display. */ for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) { t->lno = lno; t->soff = cnt; SMAP_FLUSH(t); if (vs_line(sp, t, NULL, NULL)) return (1); } /* Display the new lines at the bottom of the screen. */ for (t = TMAP - diff;;) { if (t < TMAP && vs_sm_next(sp, t, t + 1)) return (1); /* vs_sm_next() flushed the cache. */ if (vs_line(sp, ++t, NULL, NULL)) return (1); if (t == TMAP) break; } } return (0); } /* * vs_sm_scroll * Scroll the SMAP up/down count logical lines. Different * semantics based on the vi command, *sigh*. * * PUBLIC: int vs_sm_scroll(SCR *, MARK *, recno_t, scroll_t); */ int vs_sm_scroll(SCR *sp, MARK *rp, recno_t count, scroll_t scmd) { SMAP *smp; /* * Invalidate the cursor. The line is probably going to change, * (although for ^E and ^Y it may not). In any case, the scroll * routines move the cursor to draw things. */ F_SET(VIP(sp), VIP_CUR_INVALID); /* Find the cursor in the screen. */ if (vs_sm_cursor(sp, &smp)) return (1); switch (scmd) { case CNTRL_B: case CNTRL_U: case CNTRL_Y: case Z_CARAT: if (vs_sm_down(sp, rp, count, scmd, smp)) return (1); break; case CNTRL_D: case CNTRL_E: case CNTRL_F: case Z_PLUS: if (vs_sm_up(sp, rp, count, scmd, smp)) return (1); break; default: abort(); } /* * !!! * If we're at the start of a line, go for the first non-blank. * This makes it look like the old vi, even though we're moving * around by logical lines, not physical ones. * * XXX * In the presence of a long line, which has more than a screen * width of leading spaces, this code can cause a cursor warp. * Live with it. */ if (scmd != CNTRL_E && scmd != CNTRL_Y && rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno)) return (1); return (0); } /* * vs_sm_up -- * Scroll the SMAP up count logical lines. */ static int vs_sm_up(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp) { int cursor_set, echanged, zset; SMAP *ssmp, s1, s2; /* * Check to see if movement is possible. * * Get the line after the map. If that line is a new one (and if * O_LEFTRIGHT option is set, this has to be true), and the next * line doesn't exist, and the cursor doesn't move, or the cursor * isn't even on the screen, or the cursor is already at the last * line in the map, it's an error. If that test succeeded because * the cursor wasn't at the end of the map, test to see if the map * is mostly empty. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) { if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) { v_eof(sp, NULL); return (1); } if (vs_sm_next(sp, smp, &s1)) return (1); if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) { v_eof(sp, NULL); return (1); } } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves down one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^F command is always in the latter case, for historical * reasons. */ cursor_set = 0; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_F) { s1 = TMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_next(sp, &s1, &s2)) return (1); if (s2.lno != s1.lno && !db_exist(sp, s2.lno)) break; } TMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_BOTTOM)) return (1); return (vs_sm_position(sp, rp, 0, P_TOP)); } cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (vs_sm_next(sp, TMAP, &s1)) return (1); if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; *++TMAP = s1; /* vs_sm_next() flushed the cache. */ if (vs_line(sp, TMAP, NULL, NULL)) return (1); if (!cursor_set) ++ssmp; } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (echanged = zset = 0; count; --count) { /* Decide what would show up on the screen. */ if (vs_sm_next(sp, TMAP, &s1)) return (1); /* If the line doesn't exist, we're done. */ if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno)) break; /* Scroll the screen cursor up one logical line. */ if (vs_sm_1up(sp)) return (1); switch (scmd) { case CNTRL_E: if (smp > HMAP) --smp; else echanged = 1; break; case Z_PLUS: if (zset) { if (smp > HMAP) --smp; } else { smp = TMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (cursor_set) return(0); switch (scmd) { case CNTRL_E: /* * On a ^E that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (echanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case CNTRL_F: /* * If there are more lines, the ^F command is positioned at * the first line of the screen. */ if (!count) { smp = HMAP; break; } /* FALLTHROUGH */ case CNTRL_D: /* * The ^D and ^F commands move the cursor towards EOF * if there are more lines to move. Check to be sure * the lines actually exist. (They may not if the * file is smaller than the screen.) */ for (; count; --count, ++smp) if (smp == TMAP || !db_exist(sp, smp[1].lno)) break; break; case Z_PLUS: /* The z+ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_1up -- * Scroll the SMAP up one. * * PUBLIC: int vs_sm_1up(SCR *); */ int vs_sm_1up(SCR *sp) { /* * Delete the top line of the screen. Shift the screen map * up and display a new line at the bottom of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_deleteln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_next(sp, TMAP, TMAP)) return (1); } else { memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_next(sp, TMAP - 1, TMAP)) return (1); } /* vs_sm_next() flushed the cache. */ return (vs_line(sp, TMAP, NULL, NULL)); } /* * vs_deleteln -- * Delete a line a la curses, make sure to put the information * line and other screens back. */ static int vs_deleteln(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; if (IS_ONELINE(sp)) (void)gp->scr_clrtoeol(sp); else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_insertln(sp); (void)gp->scr_move(sp, oldy, oldx); } } return (0); } /* * vs_sm_down -- * Scroll the SMAP down count logical lines. */ static int vs_sm_down(SCR *sp, MARK *rp, recno_t count, scroll_t scmd, SMAP *smp) { SMAP *ssmp, s1, s2; int cursor_set, ychanged, zset; /* Check to see if movement is possible. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) && (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) { v_sof(sp, NULL); return (1); } /* * Small screens: see vs_refresh.c section 6a. * * If it's a small screen, and the movement isn't larger than a * screen, i.e some context will remain, open up the screen and * display by scrolling. In this case, the cursor moves up one * line for each line displayed. Otherwise, erase/compress and * repaint, and move the cursor to the first line in the screen. * Note, the ^B command is always in the latter case, for historical * reasons. */ cursor_set = scmd == CNTRL_Y; if (IS_SMALL(sp)) { if (count >= sp->t_maxrows || scmd == CNTRL_B) { s1 = HMAP[0]; if (vs_sm_erase(sp)) return (1); for (; count--; s1 = s2) { if (vs_sm_prev(sp, &s1, &s2)) return (1); if (s2.lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1)) break; } HMAP[0] = s2; if (vs_sm_fill(sp, OOBLNO, P_TOP)) return (1); return (vs_sm_position(sp, rp, 0, P_BOTTOM)); } cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp); for (; count && sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) { if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; ++TMAP; if (vs_sm_1down(sp)) return (1); } if (!cursor_set) { rp->lno = ssmp->lno; rp->cno = ssmp->c_sboff; } if (count == 0) return (0); } for (ychanged = zset = 0; count; --count) { /* If the line doesn't exist, we're done. */ if (HMAP->lno == 1 && (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1)) break; /* Scroll the screen and cursor down one logical line. */ if (vs_sm_1down(sp)) return (1); switch (scmd) { case CNTRL_Y: if (smp < TMAP) ++smp; else ychanged = 1; break; case Z_CARAT: if (zset) { if (smp < TMAP) ++smp; } else { smp = HMAP; zset = 1; } /* FALLTHROUGH */ default: break; } } if (scmd != CNTRL_Y && cursor_set) return(0); switch (scmd) { case CNTRL_B: /* * If there are more lines, the ^B command is positioned at * the last line of the screen. However, the line may not * exist. */ if (!count) { for (smp = TMAP; smp > HMAP; --smp) if (db_exist(sp, smp->lno)) break; break; } /* FALLTHROUGH */ case CNTRL_U: /* * The ^B and ^U commands move the cursor towards SOF * if there are more lines to move. */ if (count < smp - HMAP) smp -= count; else smp = HMAP; break; case CNTRL_Y: /* * On a ^Y that was forced to change lines, try and keep the * cursor as close as possible to the last position, but also * set it up so that the next "real" movement will return the * cursor to the closest position to the last real movement. */ if (ychanged) { rp->lno = smp->lno; rp->cno = vs_colpos(sp, smp->lno, (O_ISSET(sp, O_LEFTRIGHT) ? smp->coff : (smp->soff - 1) * sp->cols) + sp->rcm % sp->cols); } return (0); case Z_CARAT: /* The z^ command moves the cursor to the first new line. */ break; default: abort(); } if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_erase -- * Erase the small screen area for the scrolling functions. */ static int vs_sm_erase(SCR *sp) { GS *gp; gp = sp->gp; (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) { (void)gp->scr_move(sp, TMAP - HMAP, 0); (void)gp->scr_clrtoeol(sp); } return (0); } /* * vs_sm_1down -- * Scroll the SMAP down one. * * PUBLIC: int vs_sm_1down(SCR *); */ int vs_sm_1down(SCR *sp) { /* * Insert a line at the top of the screen. Shift the screen map * down and display a new line at the top of the screen. */ (void)sp->gp->scr_move(sp, 0, 0); if (vs_insertln(sp, 1)) return (1); /* One-line screens can fail. */ if (IS_ONELINE(sp)) { if (vs_sm_prev(sp, HMAP, HMAP)) return (1); } else { memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP)); if (vs_sm_prev(sp, HMAP + 1, HMAP)) return (1); } /* vs_sm_prev() flushed the cache. */ return (vs_line(sp, HMAP, NULL, NULL)); } /* * vs_insertln -- * Insert a line a la curses, make sure to put the information * line and other screens back. */ static int vs_insertln(SCR *sp, int cnt) { GS *gp; size_t oldy, oldx; gp = sp->gp; if (IS_ONELINE(sp)) { (void)gp->scr_move(sp, LASTLINE(sp), 0); (void)gp->scr_clrtoeol(sp); } else { (void)gp->scr_cursor(sp, &oldy, &oldx); while (cnt--) { (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0); (void)gp->scr_deleteln(sp); (void)gp->scr_move(sp, oldy, oldx); (void)gp->scr_insertln(sp); } } return (0); } /* * vs_sm_next -- * Fill in the next entry in the SMAP. * * PUBLIC: int vs_sm_next(SCR *, SMAP *, SMAP *); */ int vs_sm_next(SCR *sp, SMAP *p, SMAP *t) { size_t lcnt; SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno + 1; t->coff = p->coff; } else { lcnt = vs_screens(sp, p->lno, NULL); if (lcnt == p->soff) { t->lno = p->lno + 1; t->soff = 1; } else { t->lno = p->lno; t->soff = p->soff + 1; } } return (0); } /* * vs_sm_prev -- * Fill in the previous entry in the SMAP. * * PUBLIC: int vs_sm_prev(SCR *, SMAP *, SMAP *); */ int vs_sm_prev(SCR *sp, SMAP *p, SMAP *t) { SMAP_FLUSH(t); if (O_ISSET(sp, O_LEFTRIGHT)) { t->lno = p->lno - 1; t->coff = p->coff; } else { if (p->soff != 1) { t->lno = p->lno; t->soff = p->soff - 1; } else { t->lno = p->lno - 1; t->soff = vs_screens(sp, t->lno, NULL); } } return (t->lno == 0); } /* * vs_sm_cursor -- * Return the SMAP entry referenced by the cursor. * * PUBLIC: int vs_sm_cursor(SCR *, SMAP **); */ int vs_sm_cursor(SCR *sp, SMAP **smpp) { SMAP *p; /* See if the cursor is not in the map. */ if (sp->lno < HMAP->lno || sp->lno > TMAP->lno) return (1); /* Find the first occurrence of the line. */ for (p = HMAP; p->lno != sp->lno; ++p); /* Fill in the map information until we find the right line. */ for (; p <= TMAP; ++p) { /* Short lines are common and easy to detect. */ if (p != TMAP && (p + 1)->lno != p->lno) { *smpp = p; return (0); } if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL)) return (1); if (p->c_eboff >= sp->cno) { *smpp = p; return (0); } } /* It was past the end of the map after all. */ return (1); } /* * vs_sm_position -- * Return the line/column of the top, middle or last line on the screen. * (The vi H, M and L commands.) Here because only the screen routines * know what's really out there. * * PUBLIC: int vs_sm_position(SCR *, MARK *, unsigned long, pos_t); */ int vs_sm_position(SCR *sp, MARK *rp, unsigned long cnt, pos_t pos) { SMAP *smp; recno_t last; switch (pos) { case P_TOP: /* * !!! * Historically, an invalid count to the H command failed. * We do nothing special here, just making sure that H in * an empty screen works. */ if (cnt > TMAP - HMAP) goto sof; smp = HMAP + cnt; if (cnt && !db_exist(sp, smp->lno)) { sof: msgq(sp, M_BERR, "Movement past the end-of-screen"); return (1); } break; case P_MIDDLE: /* * !!! * Historically, a count to the M command was ignored. * If the screen isn't filled, find the middle of what's * real and move there. */ if (!db_exist(sp, TMAP->lno)) { if (db_last(sp, &last)) return (1); for (smp = TMAP; smp->lno > last && smp > HMAP; --smp); if (smp > HMAP) smp -= (smp - HMAP) / 2; } else smp = (HMAP + (TMAP - HMAP) / 2) + cnt; break; case P_BOTTOM: /* * !!! * Historically, an invalid count to the L command failed. * If the screen isn't filled, find the bottom of what's * real and try to offset from there. */ if (cnt > TMAP - HMAP) goto eof; smp = TMAP - cnt; if (!db_exist(sp, smp->lno)) { if (db_last(sp, &last)) return (1); for (; smp->lno > last && smp > HMAP; --smp); if (cnt > smp - HMAP) { eof: msgq(sp, M_BERR, "Movement past the beginning-of-screen"); return (1); } smp -= cnt; } break; default: abort(); } /* Make sure that the cached information is valid. */ if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL)) return (1); rp->lno = smp->lno; rp->cno = smp->c_sboff; return (0); } /* * vs_sm_nlines -- * Return the number of screen lines from an SMAP entry to the * start of some file line, less than a maximum value. * * PUBLIC: recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t); */ recno_t vs_sm_nlines(SCR *sp, SMAP *from_sp, recno_t to_lno, size_t max) { recno_t lno, lcnt; if (O_ISSET(sp, O_LEFTRIGHT)) return (from_sp->lno > to_lno ? from_sp->lno - to_lno : to_lno - from_sp->lno); if (from_sp->lno == to_lno) return (from_sp->soff - 1); if (from_sp->lno > to_lno) { lcnt = from_sp->soff - 1; /* Correct for off-by-one. */ for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } else { lno = from_sp->lno; lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1; for (; ++lno < to_lno && lcnt <= max;) lcnt += vs_screens(sp, lno, NULL); } return (lcnt); } ================================================ FILE: vi/vs_split.c ================================================ /* $OpenBSD: vs_split.c,v 1.16 2016/05/27 09:18:12 martijn Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * See the LICENSE.md file for redistribution information. */ #include #include #include #include #include #include #include #include #include #include "../common/common.h" #include "vi.h" static SCR *vs_getbg(SCR *, char *); /* * vs_split -- * Create a new screen. * * PUBLIC: int vs_split(SCR *, SCR *, int); */ int vs_split(SCR *sp, SCR *new, int ccl) { GS *gp; SMAP *smp; size_t half; int issmallscreen, splitup; gp = sp->gp; /* Check to see if it's possible. */ /* XXX: The IS_ONELINE fix will change this, too. */ if (sp->rows < 4) { msgq(sp, M_ERR, "Screen must be larger than %d lines to split", 4 - 1); return (1); } /* Wait for any messages in the screen. */ vs_resolve(sp, NULL, 1); half = sp->rows / 2; if (ccl && half > 6) half = 6; /* Get a new screen map. */ CALLOC(sp, _HMAP(new), SIZE_HMAP(sp), sizeof(SMAP)); if (_HMAP(new) == NULL) return (1); _HMAP(new)->lno = sp->lno; _HMAP(new)->coff = 0; _HMAP(new)->soff = 1; /* * Small screens: see vs_refresh.c section 6a. Set a flag so * we know to fix the screen up later. */ issmallscreen = IS_SMALL(sp); /* The columns in the screen don't change. */ new->cols = sp->cols; /* * Split the screen, and link the screens together. If creating a * screen to edit the colon command line or the cursor is in the top * half of the current screen, the new screen goes under the current * screen. Else, it goes above the current screen. * * Recalculate current cursor position based on sp->lno, we're called * with the cursor on the colon command line. Then split the screen * in half and update the shared information. */ splitup = !ccl && (vs_sm_cursor(sp, &smp) ? 0 : (smp - HMAP) + 1) >= half; if (splitup) { /* Old is bottom half. */ new->rows = sp->rows - half; /* New. */ new->woff = sp->woff; sp->rows = half; /* Old. */ sp->woff += new->rows; /* Link in before old. */ TAILQ_INSERT_BEFORE(sp, new, q); /* * If the parent is the bottom half of the screen, shift * the map down to match on-screen text. */ memmove(_HMAP(sp), _HMAP(sp) + new->rows, (sp->t_maxrows - new->rows) * sizeof(SMAP)); } else { /* Old is top half. */ new->rows = half; /* New. */ sp->rows -= half; /* Old. */ new->woff = sp->woff + sp->rows; /* Link in after old. */ TAILQ_INSERT_AFTER(&gp->dq, sp, new, q); } /* Adjust maximum text count. */ sp->t_maxrows = IS_ONELINE(sp) ? 1 : sp->rows - 1; new->t_maxrows = IS_ONELINE(new) ? 1 : new->rows - 1; /* * Small screens: see vs_refresh.c, section 6a. * * The child may have different screen options sizes than the parent, * so use them. Guarantee that text counts aren't larger than the * new screen sizes. */ if (issmallscreen) { /* Fix the text line count for the parent. */ if (splitup) sp->t_rows -= new->rows; /* Fix the parent screen. */ if (sp->t_rows > sp->t_maxrows) sp->t_rows = sp->t_maxrows; if (sp->t_minrows > sp->t_maxrows) sp->t_minrows = sp->t_maxrows; /* Fix the child screen. */ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); if (new->t_rows > new->t_maxrows) new->t_rows = new->t_maxrows; if (new->t_minrows > new->t_maxrows) new->t_minrows = new->t_maxrows; } else { sp->t_minrows = sp->t_rows = IS_ONELINE(sp) ? 1 : sp->rows - 1; /* * The new screen may be a small screen, even if the parent * was not. Don't complain if O_WINDOW is too large, we're * splitting the screen so the screen is much smaller than * normal. */ new->t_minrows = new->t_rows = O_VAL(sp, O_WINDOW); if (new->t_rows > new->rows - 1) new->t_minrows = new->t_rows = IS_ONELINE(new) ? 1 : new->rows - 1; } /* Adjust the ends of the new and old maps. */ _TMAP(sp) = IS_ONELINE(sp) ? _HMAP(sp) : _HMAP(sp) + (sp->t_rows - 1); _TMAP(new) = IS_ONELINE(new) ? _HMAP(new) : _HMAP(new) + (new->t_rows - 1); /* Reset the length of the default scroll. */ if ((sp->defscroll = sp->t_maxrows / 2) == 0) sp->defscroll = 1; if ((new->defscroll = new->t_maxrows / 2) == 0) new->defscroll = 1; /* * Initialize the screen flags: * * If we're in vi mode in one screen, we don't have to reinitialize. * This isn't just a cosmetic fix. The path goes like this: * * return into vi(), SC_SSWITCH set * call vs_refresh() with SC_STATUS set * call vs_resolve to display the status message * call vs_refresh() because the SC_SCR_VI bit isn't set * * Things go downhill at this point. * * Draw the new screen from scratch, and add a status line. */ F_SET(new, SC_SCR_REFORMAT | SC_STATUS | F_ISSET(sp, SC_EX | SC_VI | SC_SCR_VI | SC_SCR_EX)); return (0); } /* * vs_discard -- * Discard the screen, folding the real-estate into a related screen, * if one exists, and return that screen. * * PUBLIC: int vs_discard(SCR *, SCR **); */ int vs_discard(SCR *sp, SCR **spp) { SCR *nsp; dir_t dir; /* * Save the old screen's cursor information. * * XXX * If called after file_end(), and the underlying file was a tmp * file, it may have gone away. */ if (sp->frp != NULL) { sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); } /* * Add into a previous screen and then into a subsequent screen, as * they're the closest to the current screen. If that doesn't work, * there was no screen to join. */ if ((nsp = TAILQ_PREV(sp, _dqh, q))) { nsp->rows += sp->rows; sp = nsp; dir = FORWARD; } else if ((nsp = TAILQ_NEXT(sp, q))) { nsp->woff = sp->woff; nsp->rows += sp->rows; sp = nsp; dir = BACKWARD; } else { sp = NULL; dir = 0; /* unused */ } if (spp != NULL) *spp = sp; if (sp == NULL) return (0); /* * Make no effort to clean up the discarded screen's information. If * it's not exiting, we'll do the work when the user redisplays it. * * Small screens: see vs_refresh.c section 6a. Adjust text line info, * unless it's a small screen. * * Reset the length of the default scroll. */ if (!IS_SMALL(sp)) sp->t_rows = sp->t_minrows = sp->rows - 1; sp->t_maxrows = sp->rows - 1; sp->defscroll = sp->t_maxrows / 2; *(HMAP + (sp->t_rows - 1)) = *TMAP; TMAP = HMAP + (sp->t_rows - 1); /* * Draw the new screen from scratch, and add a status line. * * XXX * We could play games with the map, if this were ever to be a * performance problem, but I wrote the code a few times and it * was never clean or easy. */ switch (dir) { case FORWARD: vs_sm_fill(sp, OOBLNO, P_TOP); break; case BACKWARD: vs_sm_fill(sp, OOBLNO, P_BOTTOM); break; default: abort(); } F_SET(sp, SC_STATUS); return (0); } /* * vs_fg -- * Background the current screen, and foreground a new one. * * PUBLIC: int vs_fg(SCR *, SCR **, CHAR_T *, int); */ int vs_fg(SCR *sp, SCR **nspp, CHAR_T *name, int newscreen) { GS *gp; SCR *nsp; gp = sp->gp; if (newscreen) /* Get the specified background screen. */ nsp = vs_getbg(sp, name); else /* Swap screens. */ if (vs_swap(sp, &nsp, name)) return (1); if ((*nspp = nsp) == NULL) { msgq_str(sp, M_ERR, name, name == NULL ? "There are no background screens" : "There's no background screen editing a file named %s"); return (1); } if (newscreen) { /* Remove the new screen from the background queue. */ TAILQ_REMOVE(&gp->hq, nsp, q); /* Split the screen; if we fail, hook the screen back in. */ if (vs_split(sp, nsp, 0)) { TAILQ_INSERT_TAIL(&gp->hq, nsp, q); return (1); } } else { /* Move the old screen to the background queue. */ TAILQ_REMOVE(&gp->dq, sp, q); TAILQ_INSERT_TAIL(&gp->hq, sp, q); } return (0); } /* * vs_bg -- * Background the screen, and switch to the next one. * * PUBLIC: int vs_bg(SCR *); */ int vs_bg(SCR *sp) { GS *gp; SCR *nsp; gp = sp->gp; /* Try and join with another screen. */ if (vs_discard(sp, &nsp)) return (1); if (nsp == NULL) { msgq(sp, M_ERR, "You may not background your only displayed screen"); return (1); } /* Move the old screen to the background queue. */ TAILQ_REMOVE(&gp->dq, sp, q); TAILQ_INSERT_TAIL(&gp->hq, sp, q); /* Toss the screen map. */ free(_HMAP(sp)); _HMAP(sp) = NULL; /* Switch screens. */ sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); return (0); } /* * vs_swap -- * Swap the current screen with a backgrounded one. * * PUBLIC: int vs_swap(SCR *, SCR **, char *); */ int vs_swap(SCR *sp, SCR **nspp, char *name) { GS *gp; SCR *nsp; gp = sp->gp; /* Get the specified background screen. */ if ((*nspp = nsp = vs_getbg(sp, name)) == NULL) return (0); /* * Save the old screen's cursor information. * * XXX * If called after file_end(), and the underlying file was a tmp * file, it may have gone away. */ if (sp->frp != NULL) { sp->frp->lno = sp->lno; sp->frp->cno = sp->cno; F_SET(sp->frp, FR_CURSORSET); } /* Switch screens. */ sp->nextdisp = nsp; F_SET(sp, SC_SSWITCH); /* Initialize terminal information. */ VIP(nsp)->srows = VIP(sp)->srows; /* Initialize screen information. */ nsp->cols = sp->cols; nsp->rows = sp->rows; /* XXX: Only place in vi that sets rows. */ nsp->woff = sp->woff; /* * Small screens: see vs_refresh.c, section 6a. * * The new screens may have different screen options sizes than the * old one, so use them. Make sure that text counts aren't larger * than the new screen sizes. */ if (IS_SMALL(nsp)) { nsp->t_minrows = nsp->t_rows = O_VAL(nsp, O_WINDOW); if (nsp->t_rows > sp->t_maxrows) nsp->t_rows = nsp->t_maxrows; if (nsp->t_minrows > sp->t_maxrows) nsp->t_minrows = nsp->t_maxrows; } else nsp->t_rows = nsp->t_maxrows = nsp->t_minrows = nsp->rows - 1; /* Reset the length of the default scroll. */ nsp->defscroll = nsp->t_maxrows / 2; /* Allocate a new screen map. */ CALLOC_RET(nsp, _HMAP(nsp), SIZE_HMAP(nsp), sizeof(SMAP)); _TMAP(nsp) = _HMAP(nsp) + (nsp->t_rows - 1); /* Fill the map. */ if (vs_sm_fill(nsp, nsp->lno, P_FILL)) return (1); /* * The new screen replaces the old screen in the parent/child list. * We insert the new screen after the old one. If we're exiting, * the exit will delete the old one, if we're foregrounding, the fg * code will move the old one to the background queue. */ TAILQ_REMOVE(&gp->hq, nsp, q); TAILQ_INSERT_AFTER(&gp->dq, sp, nsp, q); /* * Don't change the screen's cursor information other than to * note that the cursor is wrong. */ F_SET(VIP(nsp), VIP_CUR_INVALID); /* Draw the new screen from scratch, and add a status line. */ F_SET(nsp, SC_SCR_REDRAW | SC_STATUS); return (0); } /* * vs_resize -- * Change the absolute size of the current screen. * * PUBLIC: int vs_resize(SCR *, long, adj_t); */ int vs_resize(SCR *sp, long count, adj_t adj) { GS *gp; SCR *g, *s; size_t g_off, s_off; gp = sp->gp; (void)gp; /* * Figure out which screens will grow, which will shrink, and * make sure it's possible. */ if (count == 0) return (0); if (adj == A_SET) { if (sp->t_maxrows == count) return (0); if (sp->t_maxrows > count) { adj = A_DECREASE; count = sp->t_maxrows - count; } else { adj = A_INCREASE; count = count - sp->t_maxrows; } } g_off = s_off = 0; if (adj == A_DECREASE) { if (count < 0) count = -count; s = sp; if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) goto toosmall; if ((g = TAILQ_PREV(sp, _dqh, q)) == NULL) { if ((g = TAILQ_NEXT(sp, q)) == NULL) goto toobig; g_off = -count; } else s_off = count; } else { g = sp; if ((s = TAILQ_NEXT(sp, q))) if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) s = NULL; else s_off = count; else s = NULL; if (s == NULL) { if ((s = TAILQ_PREV(sp, _dqh, q)) == NULL) { toobig: msgq(sp, M_BERR, adj == A_DECREASE ? "The screen cannot shrink" : "The screen cannot grow"); return (1); } if (s->t_maxrows < MINIMUM_SCREEN_ROWS + count) { toosmall: msgq(sp, M_BERR, "The screen can only shrink to %d rows", MINIMUM_SCREEN_ROWS); return (1); } g_off = -count; } } /* * Fix up the screens; we could optimize the reformatting of the * screen, but this isn't likely to be a common enough operation * to make it worthwhile. */ s->rows += -count; s->woff += s_off; g->rows += count; g->woff += g_off; g->t_rows += count; if (g->t_minrows == g->t_maxrows) g->t_minrows += count; g->t_maxrows += count; _TMAP(g) += count; F_SET(g, SC_SCR_REFORMAT | SC_STATUS); s->t_rows -= count; s->t_maxrows -= count; if (s->t_minrows > s->t_maxrows) s->t_minrows = s->t_maxrows; _TMAP(s) -= count; F_SET(s, SC_SCR_REFORMAT | SC_STATUS); return (0); } /* * vs_getbg -- * Get the specified background screen, or, if name is NULL, the first * background screen. */ static SCR * vs_getbg(SCR *sp, char *name) { GS *gp; SCR *nsp; char *p; gp = sp->gp; /* If name is NULL, return the first background screen on the list. */ if (name == NULL) return (TAILQ_FIRST(&gp->hq)); /* Search for a full match. */ TAILQ_FOREACH(nsp, &gp->hq, q) { if (!strcmp(nsp->frp->name, name)) return(nsp); } /* Search for a last-component match. */ TAILQ_FOREACH(nsp, &gp->hq, q) { if ((p = strrchr(nsp->frp->name, '/')) == NULL) p = nsp->frp->name; else ++p; if (!strcmp(p, name)) return(nsp); } return (NULL); } ================================================ FILE: xinstall/xinstall.1 ================================================ .\" $OpenBSD: install.1,v 1.31 2019/02/08 12:53:44 schwarze Exp $ .\" $NetBSD: install.1,v 1.4 1994/11/14 04:57:17 jtc Exp $ .\" .\" SPDX-License-Identifier: BSD-3-Clause .\" .\" Copyright (c) 1987, 1990, 1993 .\" The Regents of the University of California. .\" Copyright (c) 2022-2024 Jeffrey H. Johnson .\" .\" 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. .\" .\" @(#)install.1 8.1 (Berkeley) 6/6/93 .\" .Dd $Mdocdate: March 2 2022 $ .Dt XINSTALL 1 .Os .Sh NAME .Nm xinstall .Nd install binaries .Sh SYNOPSIS .Nm xinstall .Op Fl bCcDdFpSsUv .Op Fl B Ar suffix .Op Fl g Ar group .Op Fl m Ar mode .Op Fl o Ar owner .Ar source ... target ... .Sh DESCRIPTION The .Ar source file(s) are copied to the .Ar target file or directory. If the .Ar target file already exists, it is either renamed to .Ar file.bak if the .Fl b option is given or overwritten if permissions allow. An alternate backup suffix may be specified via the .Fl B option's argument. If the .Fl d option is given, .Ar target directories are created, and no files are copied. .Pp The options are as follows: .Bl -tag -width "-B suffix" .It Fl B Ar suffix Use .Ar suffix as the backup suffix if .Fl b is given. .It Fl b Backup any existing files before overwriting them by renaming them to .Ar file.bak . See .Fl B for specifying a different backup suffix. .It Fl C Compare and copy the file. If the target file already exists and the files are the same, then installation does not change the modification time of the target. .It Fl c Copy the file. This is the default. The .Fl c option is only included for backwards compatibility. .It Fl D Create all leading components of the target before installing into it. When the .Fl D option is specified, any new or existing directory components will have the default (0755) permissions applied. If the .Fl D option is specified in conjunction with .Fl m , the requested mode will be set for the target file. If more restrictive directory permissions are required, .Nm .Fl d .Fl m should be used first to create the directories, followed by .Nm .Fl c to install the files. .It Fl d Create directories, without installing files. Missing parent directories are created as required. If a directory already exists, the owner, group, and mode is set according to the values specified on the command line. This option cannot be used with the .Fl B , b , C , c , .Fl p , or .Fl s options. .It Fl F Flush the file's contents to disk. When copying a file, use the .Xr fsync 2 function to synchronize the installed file's contents with the on-disk version. .It Fl g Ar group Specify a .Ar group . A numeric GID is allowed. .It Fl m Ar mode Specify an alternate .Ar 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 M Disable use of .Xr mmap 2 when installing the target. .It Fl o Ar owner Specify an .Ar owner . A numeric UID is allowed. .It Fl p Preserve the modification time. Copy the file, as if the .Fl C (compare and copy) option is specified, except if the target file does not exist or is different, then preserve the modification time of the source file. .It Fl S Safe copy. This is the default. Using this option has no effect, and is supported only for compatibility. When installing a file, a temporary file is safely created and written first in the destination directory, then atomically renamed. This avoids both race conditions and the destruction of existing files in case of disk or system failures. .It Fl s Strip the file. The external command .Pa /usr/bin/strip is called to actually strip the file, so .Nm can be portable to a large number of systems and binary types. See below for how .Nm can be instructed to use a different program for stripping binaries. .It Fl U Indicate that .Nm is running unprivileged. Any errors while setting the owner, group, or mode of the target will be non-fatal to the installation process. .It Fl v Cause .Nm to be verbose. Progress information will be printed to standard output as directories are created and files are installed or backed up. .El .Pp The .Nm utility attempts to prevent moving a file onto itself. .Pp Installing .Pa /dev/null creates an empty file. .Sh ENVIRONMENT The .Nm utility checks for the presence of the STRIPBIN and STRIP environment variables. If defined, the assigned value will be used as the .Xr strip 1 program to run. If both variables are set, STRIP is overriding. The default strip(1) program is .Pa /usr/bin/strip . .Pp If the DONTSTRIP environment variable is present, .Nm will not strip files, ignoring any specification of the .Fl s option. .Sh FILES .Bl -tag -width INS@XXXXXX -compact .It Pa INS@XXXXXX Temporary files created in the target directory by .Xr mkstemp 3 . .El .Sh EXIT STATUS .Ex -std xinstall .Sh SEE ALSO .Xr chgrp 1 , .Xr chmod 1 , .Xr cmp 1 , .Xr cp 1 , .Xr ln 1 , .Xr mv 1 , .Xr strip 1 , .Xr mmap 2 , .Xr chown 8 .Sh HISTORY The .Nm utility first appeared in .Bx 4.2 . .Sh CAVEATS .Nm is not standardized by POSIX. Furthermore, there there is no fully compatible subset of options available across all systems which implement the .Nm utility. Even amongst the BSD-derived systems, the .Fl C , .Fl D , .Fl F , .Fl p , .Fl S , .Fl U , and .Fl v flags are non-standard, and cannot be relied upon for portability. .Pp Errors when stripping files are not fatal to the .Nm process. .Pp Temporary files may be left in the target directory if .Nm exits abnormally. .Pp .Nm options should be specified before any sources or the target, to avoid ambiguities between filenames, directories, and options when .Nm parses the command line. .Pp The exact behavior of .Nm varies depending on the operating system and filesystem. .Pp Error messages, warnings, and verbose feedback could be improved. ================================================ FILE: xinstall/xinstall.c ================================================ /* $OpenBSD: xinstall.c,v 1.78 2024/10/17 15:38:38 millert Exp $ */ /* $NetBSD: xinstall.c,v 1.9 1995/12/20 10:25:17 jonathan Exp $ */ /* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1987, 1990, 1993 * The Regents of the University of California. * Copyright (c) 2022-2024 Jeffrey H. Johnson * * 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. */ #include "../include/compat.h" #ifdef __solaris__ # define _XPG7 #endif /* ifdef __solaris__ */ #include #ifdef __illumos__ # undef madvise # define madvise posix_madvise #endif /* ifdef __illumos__ */ #ifdef _AIX # define _POSIX_SOURCE # define _XOPEN_SOURCE 700 # undef _ALL_SOURCE #endif /* ifdef _AIX */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "errc.h" #include "setmode.h" #include "minpwcache.h" #include "pathnames.h" #ifndef EFTYPE # define EFTYPE EINVAL #endif /* ifndef EFTYPE */ #undef open #define _MAXBSIZE ( 64 * 1024 ) #define MINIMUM(a, b) ((( a ) < ( b )) ? ( a ) : ( b )) #define DIRECTORY 0x01 /* Tell install it's a directory. */ #define USEFSYNC 0x04 /* Tell install to use fsync(2). */ #define BACKUP_SUFFIX ".bak" int dobackup, docompare, dodest, dodir, nommap; int dopreserve, dostrip, dounpriv, doverbose; int mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; char pathbuf[PATH_MAX], tempfile[PATH_MAX]; char *suffix = BACKUP_SUFFIX; uid_t uid = (uid_t)-1; gid_t gid = (gid_t)-1; static void copy(int, char *, int, char *, off_t, int); static int compare(int, const char *, off_t, int, const char *, off_t); static void install(char *, char *, unsigned int); static void install_dir(char *, int); static void strip(char *); static void usage(void); static int create_tempfile(char *, char *, size_t); static int file_write(int, char *, size_t, int *, int *, int); static void file_flush(int, int); int main(int argc, char *argv[]) { struct stat from_sb, to_sb; void *set; unsigned int iflags; int ch, no_target; char *to_name, *group = NULL, *owner = NULL; const char *errstr; iflags = 0; while (( ch = openbsd_getopt(argc, argv, "B:bCcDdFg:Mm:o:pSsUv")) != -1) { switch (ch) { case 'C': docompare = 1; break; case 'B': suffix = openbsd_optarg; /*FALLTHROUGH*/ /* fall through */ /* fall through; -B implies -b */ case 'b': dobackup = 1; break; case 'c': /* For backwards compatibility. */ break; case 'F': iflags |= USEFSYNC; break; case 'g': group = openbsd_optarg; break; case 'm': if (!( set = openbsd_setmode(openbsd_optarg))) { openbsd_errx(1, "%s: invalid file mode", openbsd_optarg); } mode = openbsd_getmode(set, 0); free(set); break; case 'M': nommap = 1; break; case 'o': owner = openbsd_optarg; break; case 'p': docompare = dopreserve = 1; break; case 'S': /* For backwards compatibility. */ break; case 's': if (getenv("DONTSTRIP") == NULL) dostrip = 1; break; case 'U': dounpriv = 1; break; case 'v': doverbose = 1; break; case 'D': dodest = 1; break; case 'd': dodir = 1; break; default: usage(); } } argc -= openbsd_optind; argv += openbsd_optind; /* some options make no sense when creating directories */ if (( docompare || dostrip ) && dodir) { usage(); } /* must have at least two arguments, except when creating directories */ if (argc == 0 || ( argc == 1 && !dodir )) { usage(); } /* get group and owner id's */ if (group != NULL && openbsd_gid_from_group(group, &gid) == -1) { gid = strtonum(group, 0, GID_MAX, &errstr); if (errstr != NULL) { openbsd_errx(1, "unknown group '%s'", group); } } if (owner != NULL && openbsd_uid_from_user(owner, &uid) == -1) { uid = strtonum(owner, 0, UID_MAX, &errstr); if (errstr != NULL) { openbsd_errx(1, "unknown user '%s'", owner); } } if (dodir) { for (; *argv != NULL; ++argv) { install_dir(*argv, mode); } exit(0); /*NOTREACHED*/ /* unreachable */ } if (dodest) { char *odst = strdup(argv[argc - 1]); const int wlen = strlen(odst); if ( wlen > 0 && odst[wlen-1] == '/' ) { openbsd_errc(1, EFTYPE, "%s", odst); } char *dest = openbsd_dirname(odst); if (dest == NULL) { openbsd_errx(1, "cannot determine dirname"); } /* * When -D is passed, do not chmod the directory with the mode set for * the target file. If more restrictive permissions are required then * '-d -m' ought to be used instead. */ if (strcmp(dest, ".")) install_dir(dest, 0755); } no_target = stat(to_name = argv[argc - 1], &to_sb); if (!no_target && S_ISDIR(to_sb.st_mode)) { for (; *argv != to_name; ++argv) { install(*argv, to_name, iflags | DIRECTORY); } exit(0); /*NOTREACHED*/ /* unreachable */ } /* can't do file1 file2 directory/file */ if (argc != 2) { openbsd_errx( 1, "Invalid arguments for target '%s'; verify specified options.", argv[argc - 1]); } if (!no_target) { if (stat(*argv, &from_sb)) { openbsd_err(1, "%s", *argv); } if (!S_ISREG(to_sb.st_mode)) { openbsd_errc(1, EFTYPE, "%s", to_name); } if (to_sb.st_dev == from_sb.st_dev && to_sb.st_ino == from_sb.st_ino) { openbsd_errx(1, "'%s' and '%s' are the same file", *argv, to_name); } } install(*argv, to_name, iflags); exit(0); /*NOTREACHED*/ /* unreachable */ } /* * install -- * build a path name and install the file */ static void install(char *from_name, char *to_name, unsigned int flags) { struct stat from_sb, to_sb; struct timespec ts[2]; int devnull, from_fd, to_fd, serrno, files_match = 0; char *p = NULL; char *target_name = tempfile; (void)memset((void *)&from_sb, 0, sizeof ( from_sb )); (void)memset((void *)&to_sb, 0, sizeof ( to_sb )); /* If try to install NULL file to a directory, fails. */ if (flags & DIRECTORY || strcmp(from_name, "/dev/null")) { if (stat(from_name, &from_sb)) { openbsd_err(1, "%s", from_name); } if (!S_ISREG(from_sb.st_mode)) { openbsd_errc(1, EFTYPE, "%s", from_name); } /* Build the target path. */ if (flags & DIRECTORY) { (void)snprintf(pathbuf, sizeof ( pathbuf ), "%s/%s", to_name, ( p = strrchr(from_name, '/')) ? ++p : from_name); to_name = pathbuf; } devnull = 0; } else { devnull = 1; } if (stat(to_name, &to_sb) == 0) { /* Only compare against regular files. */ if (docompare && !S_ISREG(to_sb.st_mode)) { docompare = 0; openbsd_warnc(EFTYPE, "%s", to_name); } } else if (docompare) { /* File does not exist so silently ignore compare flag. */ docompare = 0; } if (!devnull) { if (( from_fd = open(from_name, O_RDONLY)) == -1) { openbsd_err(1, "%s", from_name); } } to_fd = create_tempfile(to_name, tempfile, sizeof ( tempfile )); if (to_fd < 0) { openbsd_err(1, "%s", tempfile); } if (!devnull) { copy(from_fd, from_name, to_fd, tempfile, from_sb.st_size, ((off_t)from_sb.st_blocks * S_BLKSIZE < from_sb.st_size )); } if (dostrip) { strip(tempfile); /* * Re-open our fd on the target, in case we used a strip * that does not work in-place -- like gnu binutils strip. */ (void)close(to_fd); if (( to_fd = open(tempfile, O_RDONLY)) == -1) { openbsd_err(1, "stripping %s", to_name); } } /* * Compare the (possibly stripped) temp file to the target. */ if (docompare) { int temp_fd = to_fd; struct stat temp_sb; /* Re-open to_fd using the real target name. */ if (( to_fd = open(to_name, O_RDONLY)) == -1) { openbsd_err(1, "%s", to_name); } if (fstat(temp_fd, &temp_sb)) { serrno = errno; (void)unlink(tempfile); openbsd_errc(1, serrno, "%s", tempfile); } if (compare(temp_fd, tempfile, temp_sb.st_size, to_fd, to_name, to_sb.st_size) == 0) { /* * If target has more than one link we need to * replace it in order to snap the extra links. * Need to preserve target file times, though. */ if (to_sb.st_nlink != 1) { ts[0] = to_sb.st_atim; ts[1] = to_sb.st_mtim; (void)futimens(temp_fd, ts); } else { files_match = 1; (void)unlink(tempfile); target_name = to_name; (void)close(temp_fd); } } if (!files_match) { (void)close(to_fd); to_fd = temp_fd; } } /* * Preserve the timestamp of the source file if necessary. */ if (dopreserve && !files_match) { ts[0] = from_sb.st_atim; ts[1] = from_sb.st_mtim; (void)futimens(to_fd, ts); } /* * Set owner, group, mode for target; do the chown first, * chown may lose the setuid bits. */ if (!dounpriv && ( gid != (gid_t)-1 || uid != (uid_t)-1 ) && fchown(to_fd, uid, gid)) { serrno = errno; if (target_name == tempfile) { (void)unlink(target_name); } openbsd_errx(1, "%s: chown/chgrp: %s", target_name, strerror(serrno)); } if (!dounpriv && fchmod(to_fd, mode)) { serrno = errno; if (target_name == tempfile) { (void)unlink(target_name); } openbsd_errx(1, "%s: chmod: %s", target_name, strerror(serrno)); } if (flags & USEFSYNC) { (void)fsync(to_fd); } (void)close(to_fd); if (!devnull) { (void)close(from_fd); } /* * Move the new file into place if the files are different * or were not compared. */ if (!files_match) { if (dobackup) { char backup[PATH_MAX]; (void)snprintf(backup, PATH_MAX, "%s%s", to_name, suffix); struct stat bdst; int bdir = stat(to_name, &bdst); /* It is ok for the target file not to exist. */ int bres = 0; if (!bdir && !(S_ISDIR(bdst.st_mode))) bres = rename(to_name, backup); int berr = errno; if (bres == -1 && berr != ENOENT) { serrno = errno; (void)unlink(tempfile); openbsd_errx(1, "rename: '%s' to '%s': %s", to_name, backup, strerror(serrno)); } if (berr != ENOENT && doverbose && (!bdir && !S_ISDIR(bdst.st_mode))) (void)fprintf(stdout, "%s: created backup '%s' -> '%s'\n", bsd_getprogname(), to_name, backup); } if (rename(tempfile, to_name) == -1) { serrno = errno; (void)unlink(tempfile); openbsd_errx(1, "rename: '%s' to '%s': %s", tempfile, to_name, strerror(serrno)); } } if (doverbose) (void)fprintf(stdout, "%s: installed '%s' -> '%s' (mode %o)\n", bsd_getprogname(), from_name, to_name, mode); } /* * copy -- * copy from one file to another */ static void copy(int from_fd, char *from_name, int to_fd, char *to_name, off_t size, int sparse) { ssize_t nr, nw; int serrno; char *p = NULL; char buf[_MAXBSIZE]; if (size == 0) { return; } /* Rewind file descriptors. */ if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1) { openbsd_err(1, "lseek: %s", from_name); } if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1) { openbsd_err(1, "lseek: %s", to_name); } /* * Mmap and write if less than 2 MiB (the limit is so we don't totally * trash memory on big files. This is really a minor hack, but it wins * some CPU back. Sparse files need special treatment. */ if (!nommap && !sparse && size <= 2 * 1048576) { size_t siz; if (( p = mmap(NULL, (size_t)size, PROT_READ, MAP_PRIVATE, from_fd, (off_t)0)) == MAP_FAILED) { serrno = errno; (void)unlink(to_name); openbsd_errc(1, serrno, "%s", from_name); } #ifdef MADV_SEQUENTIAL (void)madvise(p, size, MADV_SEQUENTIAL); #endif /* ifdef MADV_SEQUENTIAL */ siz = (size_t)size; if (( nw = write(to_fd, p, siz)) != siz) { serrno = errno; (void)unlink(to_name); openbsd_errx(1, "%s: %s", to_name, strerror(nw > 0 ? EIO : serrno)); } (void)munmap(p, (size_t)size); } else { int sz, rem, isem = 1; struct stat sb; /* * Pass the blocksize of the file being written to the write * routine. If the size is zero, use the default S_BLKSIZE. */ if (fstat(to_fd, &sb) != 0 || sb.st_blksize == 0) { sz = S_BLKSIZE; } else { sz = sb.st_blksize; } rem = sz; while (( nr = read(from_fd, buf, sizeof ( buf ))) > 0) { if (sparse) { nw = file_write(to_fd, buf, nr, &rem, &isem, sz); } else { nw = write(to_fd, buf, nr); } if (nw != nr) { serrno = errno; (void)unlink(to_name); openbsd_errx(1, "%s: %s", to_name, strerror(nw > 0 ? EIO : serrno)); } } if (sparse) { file_flush(to_fd, isem); } if (nr != 0) { serrno = errno; (void)unlink(to_name); openbsd_errc(1, serrno, "%s", from_name); } } } /* * compare -- * compare two files; non-zero means files differ */ static int compare(int from_fd, const char *from_name, off_t from_len, int to_fd, const char *to_name, off_t to_len) { caddr_t p1, p2; size_t length; off_t from_off, to_off, remainder; int dfound; if (from_len == 0 && from_len == to_len) { return 0; } if (from_len != to_len) { return 1; } /* * Compare the two files being careful not to mmap * more than 2 MiB at a time. */ from_off = to_off = (off_t)0; remainder = from_len; do { length = MINIMUM(remainder, 2 * 1048576); remainder -= length; if (( p1 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, from_fd, from_off)) == MAP_FAILED) { openbsd_err(1, "%s", from_name); } if (( p2 = mmap(NULL, length, PROT_READ, MAP_PRIVATE, to_fd, to_off)) == MAP_FAILED) { openbsd_err(1, "%s", to_name); } #ifdef MADV_SEQUENTIAL if (length) { (void)madvise(p1, length, MADV_SEQUENTIAL); (void)madvise(p2, length, MADV_SEQUENTIAL); } #endif /* ifdef MADV_SEQUENTIAL */ dfound = memcmp(p1, p2, length); (void)munmap(p1, length); (void)munmap(p2, length); from_off += length; to_off += length; } while ( !dfound && remainder > 0 ); return dfound; } /* * strip -- * use strip(1) to strip the target file */ static void strip(char *to_name) { int serrno, status; char *volatile path_strip; pid_t pid; if ( issetugid() || ( path_strip = getenv("STRIP")) == NULL ) if ( issetugid() || ( path_strip = getenv("STRIPBIN")) == NULL ) { path_strip = _PATH_STRIP; } switch (( pid = fork())) { case -1: serrno = errno; (void)unlink(to_name); openbsd_errc(1, serrno, "forks"); /* FALLTHROUGH */ case 0: (void)execl(path_strip, "strip", to_name, (char *)NULL); openbsd_warn("%s", path_strip); _exit(1); default: while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { break; } } if (!WIFEXITED(status)) { (void)unlink(to_name); } } } /* * install_dir -- * build directory hierarchy */ static void install_dir(char *path, int mode) { char *p = NULL; struct stat sb; int ch; for (p = path;; ++p) { if (!*p || ( p != path && *p == '/' )) { ch = *p; *p = '\0'; if (mkdir(path, 0777)) { int mkdir_errno = errno; if (stat(path, &sb)) { /* Not there; use mkdir()s errno */ openbsd_errc(1, mkdir_errno, "%s", path); /*NOTREACHED*/ /* unreachable */ } if (!S_ISDIR(sb.st_mode)) { /* Is there, but isn't a directory */ openbsd_errc(1, ENOTDIR, "%s", path); /*NOTREACHED*/ /* unreachable */ } } if (!( *p = ch )) { break; } } } if ((( gid != (gid_t)-1 || uid != (uid_t)-1 ) && chown(path, uid, gid)) || chmod(path, mode)) { openbsd_warn("%s", path); } else { if (doverbose) (void)fprintf(stdout, "%s: created directory '%s' (mode %o)\n", bsd_getprogname(), path, mode); } } /* * usage -- * print a usage message and die */ static void usage(void) { (void)fprintf(stderr, "Usage: %s [-bCcDdFMpSsUv] [-B suffix] [-g group] [-m mode] [-o owner] source ... target ...\n", bsd_getprogname()); exit(1); /*NOTREACHED*/ /* unreachable */ } /* * create_tempfile -- * create a temporary file based on path and open it */ static int create_tempfile(char *path, char *temp, size_t tsize) { char *p = NULL; if (openbsd_strlcpy(temp, path, tsize) >= tsize) { #if defined(ENAMETOOLONG) errno = ENAMETOOLONG; #endif return(-1); } if (( p = strrchr(temp, '/')) != NULL) { p++; } else { p = temp; } *p = '\0'; if (openbsd_strlcat(temp, "INS@XXXXXXXXXX", tsize) >= tsize) { #if defined(ENAMETOOLONG) errno = ENAMETOOLONG; #endif return(-1); } return mkstemp(temp); } /* * file_write() * * Write/copy a file (during copy or archive extract). This routine knows * how to copy files with lseek holes in it. (Which are read as file * blocks containing all 0's but do not have any file blocks associated * with the data). Typical examples of these are files created by dbm * variants (.pag files). While the file size of these files are huge, the * actual storage is quite small (the files are sparse). The problem is * the holes read as all zeros so are probably stored on the archive that * way (there is no way to determine if the file block is really a hole, * we only know that a file block of all zero's can be a hole). * * At this writing, no major archive format knows how to archive files * with holes. However, on extraction (or during copy, -rw) we have to * deal with these files. Without detecting the holes, the files can * consume a lot of file space if just written to disk. This replacement * for write when passed the basic allocation size of a file system block, * uses lseek whenever it detects the input data is all 0 within that * file block. In more detail, the strategy is as follows: * * While the input is all zero keep doing an lseek. Keep track of when we * pass over file block boundaries. Only write when we hit a non zero * input. once we have written a file block, we continue to write it to * the end (we stop looking at the input). When we reach the start of the * next file block, start checking for zero blocks again. Working on file * block boundaries significantly reduces the overhead when copying files * that are NOT very sparse. This overhead (when compared to a write) is * almost below the measurement resolution on many systems. Without it, * files with holes cannot be safely copied. It does has a side effect as * it can put holes into files that did not have them before, but that is * not a problem since the file contents are unchanged (in fact it saves * file space). (Except on paging files for diskless clients. But since we * cannot determine one of those file from here, we ignore them). If this * ever ends up on a system where CTG files are supported and the holes * are not desired, just do a conditional test in those routines that * call file_write() and have it call write() instead. BEFORE CLOSING THE * FILE, make sure to call file_flush() when the last write finishes with * an empty block. A lot of file systems will not create an lseek hole at * the end. In this case we drop a single 0 at the end to force the * trailing 0's in the file. * * ---Parameters--- * * rem: how many bytes left in this file system block * isempt: have we written to the file block yet (is it empty) * sz: basic file block allocation size * cnt: number of bytes on this write * str: buffer to write * * Return: * number of bytes written, -1 on write (or lseek) error. */ static int file_write(int fd, char *str, size_t cnt, int *rem, int *isempt, int sz) { char *pt; char *end; char *st = str; /* * while we have data to process */ while (cnt) { if (!*rem) { /* * We are now at the start of file system block again * (or what we think one is...). start looking for * empty blocks again */ *isempt = 1; *rem = sz; } /* * only examine up to the end of the current file block or * remaining characters to write, whatever is smaller */ size_t wcnt = MINIMUM(cnt, *rem); cnt -= wcnt; *rem -= wcnt; if (*isempt) { /* * have not written to this block yet, so we keep * looking for zero's */ pt = st; end = st + wcnt; /* * look for a zero filled buffer */ while (( pt < end ) && ( *pt == '\0' )) { ++pt; } if (pt == end) { /* * skip, buf is empty so far */ if (lseek(fd, (off_t)wcnt, SEEK_CUR) == -1) { openbsd_warn("lseek"); return -1; } st = pt; continue; } /* * drat, the buf is not zero filled */ *isempt = 0; } /* * have non-zero data in this file system block, have to write */ if (write(fd, st, wcnt) != wcnt) { openbsd_warn("write"); return -1; } st += wcnt; } return st - str; } /* * file_flush() * when the last file block in a file is zero, many file systems will not * let us create a hole at the end. To get the last block with zeros, we * write the last BYTE with a zero (back up one byte and write a zero). */ static void file_flush(int fd, int isempt) { static char blnk[] = "\0"; /* * silly test, but make sure we are only called when the last block is * filled with all zeros. */ if (!isempt) { return; } /* * move back one byte and write a zero */ if (lseek(fd, (off_t)-1, SEEK_CUR) == -1) { openbsd_warn("Failed seek on file"); return; } if (write(fd, blnk, 1) == -1) { openbsd_warn("Failed write to file"); } return; }