diff options
80 files changed, 38779 insertions, 300 deletions
@@ -302,8 +302,10 @@ void handle_packet(apacket *p, atransport *t) { asocket *s; - D("handle_packet() %d\n", p->msg.command); - + D("handle_packet() %c%c%c%c\n", ((char*) (&(p->msg.command)))[0], + ((char*) (&(p->msg.command)))[1], + ((char*) (&(p->msg.command)))[2], + ((char*) (&(p->msg.command)))[3]); print_packet("recv", p); switch(p->msg.command){ diff --git a/adb/jdwp_service.c b/adb/jdwp_service.c index 0c26f7b..296f718 100644 --- a/adb/jdwp_service.c +++ b/adb/jdwp_service.c @@ -5,6 +5,7 @@ #include <errno.h> #include <stdio.h> #include <string.h> +#include <unistd.h> /* here's how these things work. @@ -320,6 +321,7 @@ jdwp_process_event( int socket, unsigned events, void* _proc ) struct iovec iov; char dummy = '!'; char buffer[sizeof(struct cmsghdr) + sizeof(int)]; + int flags; iov.iov_base = &dummy; iov.iov_len = 1; @@ -337,10 +339,27 @@ jdwp_process_event( int socket, unsigned events, void* _proc ) cmsg->cmsg_type = SCM_RIGHTS; ((int*)CMSG_DATA(cmsg))[0] = fd; + flags = fcntl(proc->socket,F_GETFL,0); + + if (flags == -1) { + D("failed to get cntl flags for socket %d: %s\n", + proc->pid, strerror(errno)); + goto CloseProcess; + + } + + if (fcntl(proc->socket, F_SETFL, flags & ~O_NONBLOCK) == -1) { + D("failed to remove O_NONBLOCK flag for socket %d: %s\n", + proc->pid, strerror(errno)); + goto CloseProcess; + } + for (;;) { ret = sendmsg(proc->socket, &msg, 0); - if (ret >= 0) + if (ret >= 0) { + adb_close(fd); break; + } if (errno == EINTR) continue; D("sending new file descriptor to JDWP %d failed: %s\n", @@ -354,6 +373,12 @@ jdwp_process_event( int socket, unsigned events, void* _proc ) for (n = 1; n < proc->out_count; n++) proc->out_fds[n-1] = proc->out_fds[n]; + if (fcntl(proc->socket, F_SETFL, flags) == -1) { + D("failed to set O_NONBLOCK flag for socket %d: %s\n", + proc->pid, strerror(errno)); + goto CloseProcess; + } + if (--proc->out_count == 0) fdevent_del( proc->fde, FDE_WRITE ); } diff --git a/adb/sockets.c b/adb/sockets.c index 9f1b598..43925e4 100644 --- a/adb/sockets.c +++ b/adb/sockets.c @@ -65,8 +65,11 @@ asocket *find_local_socket(unsigned id) asocket *result = NULL; adb_mutex_lock(&socket_list_lock); - for(s = local_socket_list.next; s != &local_socket_list && !result; s = s->next) { - if(s->id == id) result = s; + for (s = local_socket_list.next; s != &local_socket_list; s = s->next) { + if (s->id == id) { + result = s; + break; + } } adb_mutex_unlock(&socket_list_lock); @@ -366,7 +369,7 @@ static void local_socket_event_func(int fd, unsigned ev, void *_s) asocket *create_local_socket(int fd) { asocket *s = calloc(1, sizeof(asocket)); - if(s == 0) fatal("cannot allocate socket"); + if (s == NULL) fatal("cannot allocate socket"); install_local_socket(s); s->fd = fd; s->enqueue = local_socket_enqueue; @@ -482,7 +485,7 @@ asocket *create_remote_socket(unsigned id, atransport *t) asocket *s = calloc(1, sizeof(aremotesocket)); adisconnect* dis = &((aremotesocket*)s)->disconnect; - if(s == 0) fatal("cannot allocate socket"); + if (s == NULL) fatal("cannot allocate socket"); s->id = id; s->enqueue = remote_socket_enqueue; s->ready = remote_socket_ready; @@ -761,8 +764,7 @@ asocket *create_smart_socket(void (*action_cb)(asocket *s, const char *act)) { D("Creating smart socket \n"); asocket *s = calloc(1, sizeof(asocket)); - if(s == 0) fatal("cannot allocate socket"); - s->id = 0; + if (s == NULL) fatal("cannot allocate socket"); s->enqueue = smart_socket_enqueue; s->ready = smart_socket_ready; s->close = smart_socket_close; diff --git a/adb/usb_vendors.c b/adb/usb_vendors.c index 7cbd741..efeb2a0 100644 --- a/adb/usb_vendors.c +++ b/adb/usb_vendors.c @@ -69,6 +69,8 @@ #define VENDOR_ID_PANTECH 0x10A9 // Qualcomm's USB Vendor ID #define VENDOR_ID_QUALCOMM 0x05c6 +// On-The-Go-Video's USB Vendor ID +#define VENDOR_ID_OTGV 0x2257 // NEC's USB Vendor ID #define VENDOR_ID_NEC 0x0409 // Panasonic Mobile Communication's USB Vendor ID @@ -96,6 +98,7 @@ int builtInVendorIds[] = { VENDOR_ID_KYOCERA, VENDOR_ID_PANTECH, VENDOR_ID_QUALCOMM, + VENDOR_ID_OTGV, VENDOR_ID_NEC, VENDOR_ID_PMC, VENDOR_ID_TOSHIBA, @@ -160,7 +163,7 @@ void usb_vendors_init(void) /* builds the path to the adb vendor id file. returns 0 if success */ int build_path(char* buff, size_t len, const char* format, const char* home) { - if (snprintf(buff, len, format, home, ANDROID_PATH, ANDROID_ADB_INI) >= len) { + if (snprintf(buff, len, format, home, ANDROID_PATH, ANDROID_ADB_INI) >= (signed)len) { return 1; } diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk index 3c1cf02..ccc001b 100644 --- a/debuggerd/Android.mk +++ b/debuggerd/Android.mk @@ -5,7 +5,7 @@ ifeq ($(TARGET_ARCH),arm) LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= debuggerd.c getevent.c unwind-arm.c pr-support.c utility.c +LOCAL_SRC_FILES:= debuggerd.c getevent.c unwind-arm.c pr-support.c utility.c symbol_table.c LOCAL_CFLAGS := -Wall LOCAL_MODULE := debuggerd diff --git a/debuggerd/debuggerd.c b/debuggerd/debuggerd.c index 7c36367..24b7e72 100644 --- a/debuggerd/debuggerd.c +++ b/debuggerd/debuggerd.c @@ -63,7 +63,7 @@ static int logsocket = -1; /* Log information onto the tombstone */ void _LOG(int tfd, bool in_tombstone_only, const char *fmt, ...) { - char buf[128]; + char buf[512]; va_list ap; va_start(ap, fmt); @@ -106,10 +106,11 @@ mapinfo *parse_maps_line(char *line) mi->start = strtoul(line, 0, 16); mi->end = strtoul(line + 9, 0, 16); - /* To be filled in parse_exidx_info if the mapped section starts with + /* To be filled in parse_elf_info if the mapped section starts with * elf_header */ mi->exidx_start = mi->exidx_end = 0; + mi->symbols = 0; mi->next = 0; strcpy(mi->name, line + 49); @@ -353,7 +354,7 @@ void dump_crash_banner(int tfd, unsigned pid, unsigned tid, int sig) if(sig) dump_fault_addr(tfd, tid, sig); } -static void parse_exidx_info(mapinfo *milist, pid_t pid) +static void parse_elf_info(mapinfo *milist, pid_t pid) { mapinfo *mi; for (mi = milist; mi != NULL; mi = mi->next) { @@ -383,6 +384,9 @@ static void parse_exidx_info(mapinfo *milist, pid_t pid) break; } } + + /* Try to load symbols from this file */ + mi->symbols = symbol_table_create(mi->name); } } } @@ -420,7 +424,7 @@ void dump_crash_report(int tfd, unsigned pid, unsigned tid, bool at_fault) fclose(fp); } - parse_exidx_info(milist, tid); + parse_elf_info(milist, tid); /* If stack unwinder fails, use the default solution to dump the stack * content. @@ -439,6 +443,7 @@ void dump_crash_report(int tfd, unsigned pid, unsigned tid, bool at_fault) while(milist) { mapinfo *next = milist->next; + symbol_table_free(milist->symbols); free(milist); milist = next; } diff --git a/debuggerd/symbol_table.c b/debuggerd/symbol_table.c new file mode 100644 index 0000000..150c058 --- /dev/null +++ b/debuggerd/symbol_table.c @@ -0,0 +1,178 @@ +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/mman.h> + +#include "symbol_table.h" + +#include <linux/elf.h> + +// Compare func for qsort +static int qcompar(const void *a, const void *b) +{ + return ((struct symbol*)a)->addr - ((struct symbol*)b)->addr; +} + +// Compare func for bsearch +static int bcompar(const void *addr, const void *element) +{ + struct symbol *symbol = (struct symbol*)element; + + if((unsigned int)addr < symbol->addr) { + return -1; + } + + if((unsigned int)addr - symbol->addr >= symbol->size) { + return 1; + } + + return 0; +} + +/* + * Create a symbol table from a given file + * + * Parameters: + * filename - Filename to process + * + * Returns: + * A newly-allocated SymbolTable structure, or NULL if error. + * Free symbol table with symbol_table_free() + */ +struct symbol_table *symbol_table_create(const char *filename) +{ + struct symbol_table *table = NULL; + + // Open the file, and map it into memory + struct stat sb; + int length; + char *base; + + int fd = open(filename, O_RDONLY); + + if(fd < 0) { + goto out; + } + + fstat(fd, &sb); + length = sb.st_size; + + base = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); + + if(!base) { + goto out_close; + } + + // Parse the file header + Elf32_Ehdr *hdr = (Elf32_Ehdr*)base; + Elf32_Shdr *shdr = (Elf32_Shdr*)(base + hdr->e_shoff); + + // Search for the dynamic symbols section + int dynsym_idx = -1; + int i; + + for(i = 0; i < hdr->e_shnum; i++) { + if(shdr[i].sh_type == SHT_DYNSYM ) { + dynsym_idx = i; + } + } + + if(dynsym_idx == -1) { + goto out_unmap; + } + + Elf32_Sym *dynsyms = (Elf32_Sym*)(base + shdr[dynsym_idx].sh_offset); + int numsyms = shdr[dynsym_idx].sh_size / shdr[dynsym_idx].sh_entsize; + + table = malloc(sizeof(struct symbol_table)); + if(!table) { + goto out_unmap; + } + table->num_symbols = 0; + + // Iterate through the dynamic symbol table, and count how many symbols + // are actually defined + for(i = 0; i < numsyms; i++) { + if(dynsyms[i].st_shndx != SHN_UNDEF) { + table->num_symbols++; + } + } + + int dynstr_idx = shdr[dynsym_idx].sh_link; + char *dynstr = base + shdr[dynstr_idx].sh_offset; + + // Now, create an entry in our symbol table structure for each symbol... + table->symbols = malloc(table->num_symbols * sizeof(struct symbol)); + if(!table->symbols) { + free(table); + table = NULL; + goto out_unmap; + } + + // ...and populate them + int j = 0; + for(i = 0; i < numsyms; i++) { + if(dynsyms[i].st_shndx != SHN_UNDEF) { + table->symbols[j].name = strdup(dynstr + dynsyms[i].st_name); + table->symbols[j].addr = dynsyms[i].st_value; + table->symbols[j].size = dynsyms[i].st_size; + j++; + } + } + + // Sort the symbol table entries, so they can be bsearched later + qsort(table->symbols, table->num_symbols, sizeof(struct symbol), qcompar); + +out_unmap: + munmap(base, length); + +out_close: + close(fd); + +out: + return table; +} + +/* + * Free a symbol table + * + * Parameters: + * table - Table to free + */ +void symbol_table_free(struct symbol_table *table) +{ + int i; + + if(!table) { + return; + } + + for(i=0; i<table->num_symbols; i++) { + free(table->symbols[i].name); + } + + free(table->symbols); + free(table); +} + +/* + * Search for an address in the symbol table + * + * Parameters: + * table - Table to search in + * addr - Address to search for. + * + * Returns: + * A pointer to the Symbol structure corresponding to the + * symbol which contains this address, or NULL if no symbol + * contains it. + */ +const struct symbol *symbol_table_lookup(struct symbol_table *table, unsigned int addr) +{ + if(!table) { + return NULL; + } + + return bsearch((void*)addr, table->symbols, table->num_symbols, sizeof(struct symbol), bcompar); +} diff --git a/debuggerd/symbol_table.h b/debuggerd/symbol_table.h new file mode 100644 index 0000000..d9d2520 --- /dev/null +++ b/debuggerd/symbol_table.h @@ -0,0 +1,19 @@ +#ifndef SYMBOL_TABLE_H +#define SYMBOL_TABLE_H + +struct symbol { + unsigned int addr; + unsigned int size; + char *name; +}; + +struct symbol_table { + struct symbol *symbols; + int num_symbols; +}; + +struct symbol_table *symbol_table_create(const char *filename); +void symbol_table_free(struct symbol_table *table); +const struct symbol *symbol_table_lookup(struct symbol_table *table, unsigned int addr); + +#endif diff --git a/debuggerd/unwind-arm.c b/debuggerd/unwind-arm.c index 9642d2e..b081161 100644 --- a/debuggerd/unwind-arm.c +++ b/debuggerd/unwind-arm.c @@ -37,6 +37,8 @@ #include <unwind.h> #include "utility.h" +#include "symbol_table.h" + typedef struct _ZSt9type_info type_info; /* This names C++ type_info type */ void __attribute__((weak)) __cxa_call_unexpected(_Unwind_Control_Block *ucbp); @@ -393,6 +395,7 @@ static _Unwind_Reason_Code log_function(_Unwind_Context *context, pid_t pid, phase2_vrs *vrs = (phase2_vrs*) context; const mapinfo *mi; bool only_in_tombstone = !at_fault; + const struct symbol* sym = 0; if (stack_level < STACK_CONTENT_DEPTH) { sp_list[stack_level] = vrs->core.r[R_SP]; @@ -451,9 +454,20 @@ static _Unwind_Reason_Code log_function(_Unwind_Context *context, pid_t pid, rel_pc = pc; mi = pc_to_mapinfo(map, pc, &rel_pc); - _LOG(tfd, only_in_tombstone, - " #%02d pc %08x %s\n", stack_level, rel_pc, - mi ? mi->name : ""); + /* See if we can determine what symbol this stack frame resides in */ + if (mi != 0 && mi->symbols != 0) { + sym = symbol_table_lookup(mi->symbols, rel_pc); + } + + if (sym) { + _LOG(tfd, only_in_tombstone, + " #%02d pc %08x %s (%s)\n", stack_level, rel_pc, + mi ? mi->name : "", sym->name); + } else { + _LOG(tfd, only_in_tombstone, + " #%02d pc %08x %s\n", stack_level, rel_pc, + mi ? mi->name : ""); + } return _URC_NO_REASON; } diff --git a/debuggerd/utility.h b/debuggerd/utility.h index 49f5951..2ffdf56 100644 --- a/debuggerd/utility.h +++ b/debuggerd/utility.h @@ -21,6 +21,8 @@ #include <stddef.h> #include <stdbool.h> +#include "symbol_table.h" + #ifndef PT_ARM_EXIDX #define PT_ARM_EXIDX 0x70000001 /* .ARM.exidx segment */ #endif @@ -33,6 +35,7 @@ typedef struct mapinfo { unsigned end; unsigned exidx_start; unsigned exidx_end; + struct symbol_table *symbols; char name[]; } mapinfo; diff --git a/fastboot/engine.c b/fastboot/engine.c index 8ba202c..48073ee 100644 --- a/fastboot/engine.c +++ b/fastboot/engine.c @@ -97,14 +97,20 @@ static Action *queue_action(unsigned op, const char *fmt, ...) { Action *a; va_list ap; + size_t cmdsize; a = calloc(1, sizeof(Action)); if (a == 0) die("out of memory"); va_start(ap, fmt); - vsprintf(a->cmd, fmt, ap); + cmdsize = vsnprintf(a->cmd, sizeof(a->cmd), fmt, ap); va_end(ap); + if (cmdsize >= sizeof(a->cmd)) { + free(a); + die("Command length (%d) exceeds maximum size (%d)", cmdsize, sizeof(a->cmd)); + } + if (action_last) { action_last->next = a; } else { diff --git a/include/arch/target_linux-x86/AndroidConfig.h b/include/arch/target_linux-x86/AndroidConfig.h index b9800dd..2152d6a 100644 --- a/include/arch/target_linux-x86/AndroidConfig.h +++ b/include/arch/target_linux-x86/AndroidConfig.h @@ -227,7 +227,7 @@ /* * Define if we have Linux's dbus */ -#define HAVE_DBUS 1 +/* #define HAVE_DBUS 1 */ /* * Define if tm struct has tm_gmtoff field diff --git a/include/cutils/log.h b/include/cutils/log.h index dd47c35..f602017 100644 --- a/include/cutils/log.h +++ b/include/cutils/log.h @@ -291,11 +291,11 @@ extern "C" { */ #define LOG_ALWAYS_FATAL_IF(cond, ...) \ ( (CONDITION(cond)) \ - ? ((void)android_printAssert(#cond, LOG_TAG, __VA_ARGS__)) \ + ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \ : (void)0 ) #define LOG_ALWAYS_FATAL(...) \ - ( ((void)android_printAssert(NULL, LOG_TAG, __VA_ARGS__)) ) + ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) ) /* * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that @@ -308,7 +308,7 @@ extern "C" { #else -#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, __VA_ARGS__) +#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__) #define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__) #endif @@ -317,7 +317,7 @@ extern "C" { * Assertion that generates a log message when the assertion fails. * Stripped out of release builds. Uses the current LOG_TAG. */ -#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), __VA_ARGS__) +#define LOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__) //#define LOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond) // --------------------------------------------------------------------- @@ -403,8 +403,24 @@ typedef enum { #define android_vprintLog(prio, cond, tag, fmt...) \ __android_log_vprint(prio, tag, fmt) +/* XXX Macros to work around syntax errors in places where format string + * arg is not passed to LOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF + * (happens only in debug builds). + */ + +/* Returns 2nd arg. Used to substitute default value if caller's vararg list + * is empty. + */ +#define __android_second(dummy, second, ...) second + +/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise + * returns nothing. + */ +#define __android_rest(first, ...) , ## __VA_ARGS__ + #define android_printAssert(cond, tag, fmt...) \ - __android_log_assert(cond, tag, fmt) + __android_log_assert(cond, tag, \ + __android_second(0, ## fmt, NULL) __android_rest(fmt)) #define android_writeLog(prio, tag, text) \ __android_log_write(prio, tag, text) @@ -413,7 +429,7 @@ typedef enum { __android_log_bwrite(tag, payload, len) #define android_btWriteLog(tag, type, payload, len) \ __android_log_btwrite(tag, type, payload, len) - + // TODO: remove these prototypes and their users #define android_testLog(prio, tag) (1) #define android_writevLog(vec,num) do{}while(0) diff --git a/include/netutils/dhcp.h b/include/netutils/dhcp.h new file mode 100644 index 0000000..96798c5 --- /dev/null +++ b/include/netutils/dhcp.h @@ -0,0 +1,40 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _NETUTILS_DHCP_H_ +#define _NETUTILS_DHCP_H_ + +#include <sys/cdefs.h> +#include <arpa/inet.h> + +__BEGIN_DECLS + +extern int do_dhcp(char *iname); +extern int dhcp_do_request(const char *ifname, + in_addr_t *ipaddr, + in_addr_t *gateway, + in_addr_t *mask, + in_addr_t *dns1, + in_addr_t *dns2, + in_addr_t *server, + uint32_t *lease); +extern int dhcp_stop(const char *ifname); +extern int dhcp_release_lease(const char *ifname); +extern char *dhcp_get_errmsg(); + +__END_DECLS + +#endif /* _NETUTILS_DHCP_H_ */ diff --git a/include/netutils/ifc.h b/include/netutils/ifc.h new file mode 100644 index 0000000..e245262 --- /dev/null +++ b/include/netutils/ifc.h @@ -0,0 +1,64 @@ +/* + * Copyright 2008, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _NETUTILS_IFC_H_ +#define _NETUTILS_IFC_H_ + +#include <sys/cdefs.h> +#include <arpa/inet.h> + +__BEGIN_DECLS + +extern int ifc_init(void); +extern void ifc_close(void); + +extern int ifc_get_ifindex(const char *name, int *if_indexp); +extern int ifc_get_hwaddr(const char *name, void *ptr); + +extern int ifc_up(const char *name); +extern int ifc_down(const char *name); + +extern int ifc_enable(const char *ifname); +extern int ifc_disable(const char *ifname); + +extern int ifc_reset_connections(const char *ifname); + +extern int ifc_set_addr(const char *name, in_addr_t addr); +extern int ifc_set_mask(const char *name, in_addr_t mask); +extern int ifc_set_hwaddr(const char *name, const void *ptr); + +/* This function is deprecated. Use ifc_add_route instead. */ +extern int ifc_add_host_route(const char *name, in_addr_t addr); +extern int ifc_remove_host_routes(const char *name); +extern int ifc_get_default_route(const char *ifname); +/* This function is deprecated. Use ifc_add_route instead */ +extern int ifc_set_default_route(const char *ifname, in_addr_t gateway); +/* This function is deprecated. Use ifc_add_route instead */ +extern int ifc_create_default_route(const char *name, in_addr_t addr); +extern int ifc_remove_default_route(const char *ifname); +extern int ifc_add_route(const char *name, const char *addr, int prefix_length, + const char *gw); + +extern int ifc_get_info(const char *name, in_addr_t *addr, in_addr_t *mask, + in_addr_t *flags); + +extern int ifc_configure(const char *ifname, in_addr_t address, + in_addr_t netmask, in_addr_t gateway, + in_addr_t dns1, in_addr_t dns2); + +__END_DECLS + +#endif /* _NETUTILS_IFC_H_ */ diff --git a/include/pixelflinger/pixelflinger.h b/include/pixelflinger/pixelflinger.h index dca0b90..8a2b442 100644 --- a/include/pixelflinger/pixelflinger.h +++ b/include/pixelflinger/pixelflinger.h @@ -315,7 +315,7 @@ extern "C" { ssize_t gglInit(GGLContext** context); ssize_t gglUninit(GGLContext* context); -GGLint gglBitBlti( +GGLint gglBitBlit( GGLContext* c, int tmu, GGLint crop[4], diff --git a/include/private/android_filesystem_config.h b/include/private/android_filesystem_config.h index cca09ce..d531edd 100644 --- a/include/private/android_filesystem_config.h +++ b/include/private/android_filesystem_config.h @@ -52,6 +52,8 @@ #define AID_VPN 1016 /* vpn system */ #define AID_KEYSTORE 1017 /* keystore subsystem */ #define AID_USB 1018 /* USB devices */ +#define AID_DRM 1019 /* DRM server */ +#define AID_DRMIO 1020 /* DRM IO server */ #define AID_SHELL 2000 /* adb and debug shell user */ #define AID_CACHE 2001 /* cache access */ @@ -93,6 +95,8 @@ static const struct android_id_info android_ids[] = { { "adb", AID_ADB, }, { "install", AID_INSTALL, }, { "media", AID_MEDIA, }, + { "drm", AID_DRM, }, + { "drmio", AID_DRMIO, }, { "shell", AID_SHELL, }, { "cache", AID_CACHE, }, { "diag", AID_DIAG, }, @@ -166,6 +170,7 @@ static struct fs_path_config android_files[] = { { 00640, AID_SYSTEM, AID_SYSTEM, "system/etc/bluetooth/auto_pairing.conf" }, { 00444, AID_RADIO, AID_AUDIO, "system/etc/AudioPara4.csv" }, { 00555, AID_ROOT, AID_ROOT, "system/etc/ppp/*" }, + { 00555, AID_ROOT, AID_ROOT, "system/etc/rc.*" }, { 00644, AID_SYSTEM, AID_SYSTEM, "data/app/*" }, { 00644, AID_SYSTEM, AID_SYSTEM, "data/app-private/*" }, { 00644, AID_APP, AID_APP, "data/data/*" }, diff --git a/init/util.c b/init/util.c index 377754b..d8ec88e 100644..100755 --- a/init/util.c +++ b/init/util.c @@ -439,8 +439,9 @@ void get_hardware_name(char *hardware, unsigned int *revision) if (x) { x += 2; n = 0; - while (*x && !isspace(*x)) { - hardware[n++] = tolower(*x); + while (*x && *x != '\n') { + if (!isspace(*x)) + hardware[n++] = tolower(*x); x++; if (n == 31) break; } diff --git a/libdiskconfig/Android.mk b/libdiskconfig/Android.mk index 1d0ecb4..c887955 100644 --- a/libdiskconfig/Android.mk +++ b/libdiskconfig/Android.mk @@ -3,17 +3,25 @@ include $(CLEAR_VARS) ifneq ($(TARGET_SIMULATOR),true) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ +commonSources := \ diskconfig.c \ diskutils.c \ write_lst.c \ config_mbr.c +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(commonSources) LOCAL_MODULE := libdiskconfig LOCAL_SYSTEM_SHARED_LIBRARIES := libcutils liblog libc - include $(BUILD_SHARED_LIBRARY) +ifeq ($(HOST_OS),linux) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(commonSources) +LOCAL_MODULE := libdiskconfig_host +LOCAL_SYSTEM_SHARED_LIBRARIES := libcutils +LOCAL_CFLAGS := -O2 -g -W -Wall -Werror -D_LARGEFILE64_SOURCE +include $(BUILD_HOST_STATIC_LIBRARY) +endif # HOST_OS == linux + endif # ! TARGET_SIMULATOR diff --git a/liblog/logd_write.c b/liblog/logd_write.c index 9923bba..a0a753b 100644 --- a/liblog/logd_write.c +++ b/liblog/logd_write.c @@ -56,7 +56,7 @@ static int log_fds[(int)LOG_ID_MAX] = { -1, -1, -1, -1 }; * the simulator rather than a desktop tool and want to use the device. */ static enum { - kLogUninitialized, kLogNotAvailable, kLogAvailable + kLogUninitialized, kLogNotAvailable, kLogAvailable } g_log_status = kLogUninitialized; int __android_log_dev_available(void) { @@ -189,7 +189,7 @@ int __android_log_buf_write(int bufID, int prio, const char *tag, const char *ms int __android_log_vprint(int prio, const char *tag, const char *fmt, va_list ap) { - char buf[LOG_BUF_SIZE]; + char buf[LOG_BUF_SIZE]; vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); @@ -223,12 +223,23 @@ int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fm void __android_log_assert(const char *cond, const char *tag, const char *fmt, ...) { - va_list ap; - char buf[LOG_BUF_SIZE]; + char buf[LOG_BUF_SIZE]; - va_start(ap, fmt); - vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); - va_end(ap); + if (fmt) { + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + va_end(ap); + } else { + /* Msg not provided, log condition. N.B. Do not use cond directly as + * format string as it could contain spurious '%' syntax (e.g. + * "%d" in "blocks%devs == 0"). + */ + if (cond) + snprintf(buf, LOG_BUF_SIZE, "Assertion failed: %s", cond); + else + strcpy(buf, "Unspecified assertion failed"); + } __android_log_write(ANDROID_LOG_FATAL, tag, buf); diff --git a/libnetutils/dhcp_utils.c b/libnetutils/dhcp_utils.c index 0f8a6c4..cb0960f 100644 --- a/libnetutils/dhcp_utils.c +++ b/libnetutils/dhcp_utils.c @@ -115,6 +115,14 @@ static void fill_ip_info(const char *interface, } } +static const char *ipaddr_to_string(in_addr_t addr) +{ + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); +} + /* * Start the dhcp client daemon, and wait for it to finish * configuring the interface. @@ -165,7 +173,13 @@ int dhcp_do_request(const char *interface, return -1; } if (strcmp(prop_value, "ok") == 0) { + char dns_prop_name[PROPERTY_KEY_MAX]; fill_ip_info(interface, ipaddr, gateway, mask, dns1, dns2, server, lease); + /* copy the dhcp.XXX.dns properties to net.XXX.dns */ + snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", interface); + property_set(dns_prop_name, *dns1 ? ipaddr_to_string(*dns1) : ""); + snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", interface); + property_set(dns_prop_name, *dns2 ? ipaddr_to_string(*dns2) : ""); return 0; } else { snprintf(errmsg, sizeof(errmsg), "DHCP result was %s", prop_value); diff --git a/libnetutils/dhcpclient.c b/libnetutils/dhcpclient.c index 6755ba1..ff00432 100644 --- a/libnetutils/dhcpclient.c +++ b/libnetutils/dhcpclient.c @@ -36,8 +36,8 @@ #include <dirent.h> +#include <netutils/ifc.h> #include "dhcpmsg.h" -#include "ifc_utils.h" #include "packet.h" #define VERBOSE 2 @@ -85,16 +85,12 @@ int fatal(const char *reason) // exit(1); } -const char *ipaddr(uint32_t addr) +const char *ipaddr(in_addr_t addr) { - static char buf[32]; - - sprintf(buf,"%d.%d.%d.%d", - addr & 255, - ((addr >> 8) & 255), - ((addr >> 16) & 255), - (addr >> 24)); - return buf; + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); } typedef struct dhcp_info dhcp_info; @@ -128,31 +124,11 @@ void get_dhcp_info(uint32_t *ipaddr, uint32_t *gateway, uint32_t *mask, *lease = last_good_info.lease; } -static int ifc_configure(const char *ifname, dhcp_info *info) +static int dhcp_configure(const char *ifname, dhcp_info *info) { - char dns_prop_name[PROPERTY_KEY_MAX]; - - if (ifc_set_addr(ifname, info->ipaddr)) { - printerr("failed to set ipaddr %s: %s\n", ipaddr(info->ipaddr), strerror(errno)); - return -1; - } - if (ifc_set_mask(ifname, info->netmask)) { - printerr("failed to set netmask %s: %s\n", ipaddr(info->netmask), strerror(errno)); - return -1; - } - if (ifc_create_default_route(ifname, info->gateway)) { - printerr("failed to set default route %s: %s\n", ipaddr(info->gateway), strerror(errno)); - return -1; - } - - snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", ifname); - property_set(dns_prop_name, info->dns1 ? ipaddr(info->dns1) : ""); - snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", ifname); - property_set(dns_prop_name, info->dns2 ? ipaddr(info->dns2) : ""); - last_good_info = *info; - - return 0; + return ifc_configure(ifname, info->ipaddr, info->netmask, info->gateway, + info->dns1, info->dns2); } static const char *dhcp_type_to_name(uint32_t type) @@ -449,7 +425,7 @@ int dhcp_init_ifc(const char *ifname) printerr("timed out\n"); if ( info.type == DHCPOFFER ) { printerr("no acknowledgement from DHCP server\nconfiguring %s with offered parameters\n", ifname); - return ifc_configure(ifname, &info); + return dhcp_configure(ifname, &info); } errno = ETIME; close(s); @@ -530,7 +506,7 @@ int dhcp_init_ifc(const char *ifname) if (info.type == DHCPACK) { printerr("configuring %s\n", ifname); close(s); - return ifc_configure(ifname, &info); + return dhcp_configure(ifname, &info); } else if (info.type == DHCPNAK) { printerr("configuration request denied\n"); close(s); diff --git a/libnetutils/ifc_utils.c b/libnetutils/ifc_utils.c index bde336f..0ca5fe6 100644 --- a/libnetutils/ifc_utils.c +++ b/libnetutils/ifc_utils.c @@ -27,8 +27,12 @@ #include <arpa/inet.h> #include <linux/if.h> +#include <linux/if_ether.h> +#include <linux/if_arp.h> #include <linux/sockios.h> #include <linux/route.h> +#include <linux/ipv6_route.h> +#include <netdb.h> #include <linux/wireless.h> #ifdef ANDROID @@ -43,9 +47,10 @@ #endif static int ifc_ctl_sock = -1; +static int ifc_ctl_sock6 = -1; void printerr(char *fmt, ...); -static const char *ipaddr_to_string(uint32_t addr) +static const char *ipaddr_to_string(in_addr_t addr) { struct in_addr in_addr; @@ -64,6 +69,17 @@ int ifc_init(void) return ifc_ctl_sock < 0 ? -1 : 0; } +int ifc_init6(void) +{ + if (ifc_ctl_sock6 == -1) { + ifc_ctl_sock6 = socket(AF_INET6, SOCK_DGRAM, 0); + if (ifc_ctl_sock6 < 0) { + printerr("socket() failed: %s\n", strerror(errno)); + } + } + return ifc_ctl_sock6 < 0 ? -1 : 0; +} + void ifc_close(void) { if (ifc_ctl_sock != -1) { @@ -72,6 +88,14 @@ void ifc_close(void) } } +void ifc_close6(void) +{ + if (ifc_ctl_sock6 != -1) { + (void)close(ifc_ctl_sock6); + ifc_ctl_sock6 = -1; + } +} + static void ifc_init_ifr(const char *name, struct ifreq *ifr) { memset(ifr, 0, sizeof(struct ifreq)); @@ -88,7 +112,7 @@ int ifc_get_hwaddr(const char *name, void *ptr) r = ioctl(ifc_ctl_sock, SIOCGIFHWADDR, &ifr); if(r < 0) return -1; - memcpy(ptr, &ifr.ifr_hwaddr.sa_data, 6); + memcpy(ptr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); return 0; } @@ -143,6 +167,17 @@ int ifc_set_addr(const char *name, in_addr_t addr) return ioctl(ifc_ctl_sock, SIOCSIFADDR, &ifr); } +int ifc_set_hwaddr(const char *name, const void *ptr) +{ + int r; + struct ifreq ifr; + ifc_init_ifr(name, &ifr); + + ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER; + memcpy(&ifr.ifr_hwaddr.sa_data, ptr, ETH_ALEN); + return ioctl(ifc_ctl_sock, SIOCSIFHWADDR, &ifr); +} + int ifc_set_mask(const char *name, in_addr_t mask) { struct ifreq ifr; @@ -185,45 +220,80 @@ int ifc_get_info(const char *name, in_addr_t *addr, in_addr_t *mask, unsigned *f return 0; } - -int ifc_create_default_route(const char *name, in_addr_t addr) +in_addr_t get_ipv4_netmask(int prefix_length) { - struct rtentry rt; + in_addr_t mask = 0; - memset(&rt, 0, sizeof(rt)); - - rt.rt_dst.sa_family = AF_INET; - rt.rt_flags = RTF_UP | RTF_GATEWAY; - rt.rt_dev = (void*) name; - init_sockaddr_in(&rt.rt_genmask, 0); - init_sockaddr_in(&rt.rt_gateway, addr); - - return ioctl(ifc_ctl_sock, SIOCADDRT, &rt); + mask = ~mask << (32 - prefix_length); + mask = htonl(mask); + + return mask; } -int ifc_add_host_route(const char *name, in_addr_t addr) +int ifc_add_ipv4_route(const char *ifname, struct in_addr dst, int prefix_length, + struct in_addr gw) { struct rtentry rt; int result; + in_addr_t netmask; memset(&rt, 0, sizeof(rt)); - + rt.rt_dst.sa_family = AF_INET; - rt.rt_flags = RTF_UP | RTF_HOST; - rt.rt_dev = (void*) name; - init_sockaddr_in(&rt.rt_dst, addr); - init_sockaddr_in(&rt.rt_genmask, 0); - init_sockaddr_in(&rt.rt_gateway, 0); - + rt.rt_dev = (void*) ifname; + + netmask = get_ipv4_netmask(prefix_length); + init_sockaddr_in(&rt.rt_genmask, netmask); + init_sockaddr_in(&rt.rt_dst, dst.s_addr); + rt.rt_flags = RTF_UP; + + if (prefix_length == 32) { + rt.rt_flags |= RTF_HOST; + } + + if (gw.s_addr != 0) { + rt.rt_flags |= RTF_GATEWAY; + init_sockaddr_in(&rt.rt_gateway, gw.s_addr); + } + ifc_init(); + + if (ifc_ctl_sock < 0) { + return -errno; + } + result = ioctl(ifc_ctl_sock, SIOCADDRT, &rt); - if (result < 0 && errno == EEXIST) { - result = 0; + if (result < 0) { + if (errno == EEXIST) { + result = 0; + } else { + result = -errno; + } } ifc_close(); return result; } +int ifc_create_default_route(const char *name, in_addr_t gw) +{ + struct in_addr in_dst, in_gw; + + in_dst.s_addr = 0; + in_gw.s_addr = gw; + + return ifc_add_ipv4_route(name, in_dst, 0, in_gw); +} + +int ifc_add_host_route(const char *name, in_addr_t dst) +{ + struct in_addr in_dst, in_gw; + + in_dst.s_addr = dst; + in_gw.s_addr = 0; + + return ifc_add_ipv4_route(name, in_dst, 32, in_gw); +} + int ifc_enable(const char *ifname) { int result; @@ -429,10 +499,119 @@ ifc_configure(const char *ifname, ifc_close(); - snprintf(dns_prop_name, sizeof(dns_prop_name), "dhcp.%s.dns1", ifname); + snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns1", ifname); property_set(dns_prop_name, dns1 ? ipaddr_to_string(dns1) : ""); - snprintf(dns_prop_name, sizeof(dns_prop_name), "dhcp.%s.dns2", ifname); + snprintf(dns_prop_name, sizeof(dns_prop_name), "net.%s.dns2", ifname); property_set(dns_prop_name, dns2 ? ipaddr_to_string(dns2) : ""); return 0; } + +int ifc_add_ipv6_route(const char *ifname, struct in6_addr dst, int prefix_length, + struct in6_addr gw) +{ + struct in6_rtmsg rtmsg; + int result; + int ifindex; + + memset(&rtmsg, 0, sizeof(rtmsg)); + + ifindex = if_nametoindex(ifname); + if (ifindex == 0) { + printerr("if_nametoindex() failed: interface %s\n", ifname); + return -ENXIO; + } + + rtmsg.rtmsg_ifindex = ifindex; + rtmsg.rtmsg_dst = dst; + rtmsg.rtmsg_dst_len = prefix_length; + rtmsg.rtmsg_flags = RTF_UP; + + if (prefix_length == 128) { + rtmsg.rtmsg_flags |= RTF_HOST; + } + + if (memcmp(&gw, &in6addr_any, sizeof(in6addr_any))) { + rtmsg.rtmsg_flags |= RTF_GATEWAY; + rtmsg.rtmsg_gateway = gw; + } + + ifc_init6(); + + if (ifc_ctl_sock6 < 0) { + return -errno; + } + + result = ioctl(ifc_ctl_sock6, SIOCADDRT, &rtmsg); + if (result < 0) { + if (errno == EEXIST) { + result = 0; + } else { + result = -errno; + } + } + ifc_close6(); + return result; +} + +int ifc_add_route(const char *ifname, const char *dst, int prefix_length, + const char *gw) +{ + int ret = 0; + struct sockaddr_in ipv4_dst, ipv4_gw; + struct sockaddr_in6 ipv6_dst, ipv6_gw; + struct addrinfo hints, *addr_ai, *gw_ai; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_flags = AI_NUMERICHOST; + + ret = getaddrinfo(dst, NULL, &hints, &addr_ai); + + if (ret != 0) { + printerr("getaddrinfo failed: invalid address %s\n", dst); + return -EINVAL; + } + + if (gw == NULL) { + if (addr_ai->ai_family == AF_INET6) { + gw = "::"; + } else if (addr_ai->ai_family == AF_INET) { + gw = "0.0.0.0"; + } + } + + ret = getaddrinfo(gw, NULL, &hints, &gw_ai); + if (ret != 0) { + printerr("getaddrinfo failed: invalid gateway %s\n", gw); + freeaddrinfo(addr_ai); + return -EINVAL; + } + + if (addr_ai->ai_family != gw_ai->ai_family) { + printerr("ifc_add_route: different address families: %s and %s\n", dst, gw); + freeaddrinfo(addr_ai); + freeaddrinfo(gw_ai); + return -EINVAL; + } + + if (addr_ai->ai_family == AF_INET6) { + memcpy(&ipv6_dst, addr_ai->ai_addr, sizeof(struct sockaddr_in6)); + memcpy(&ipv6_gw, gw_ai->ai_addr, sizeof(struct sockaddr_in6)); + ret = ifc_add_ipv6_route(ifname, ipv6_dst.sin6_addr, prefix_length, + ipv6_gw.sin6_addr); + } else if (addr_ai->ai_family == AF_INET) { + memcpy(&ipv4_dst, addr_ai->ai_addr, sizeof(struct sockaddr_in)); + memcpy(&ipv4_gw, gw_ai->ai_addr, sizeof(struct sockaddr_in)); + ret = ifc_add_ipv4_route(ifname, ipv4_dst.sin_addr, prefix_length, + ipv4_gw.sin_addr); + } else { + printerr("ifc_add_route: getaddrinfo returned un supported address family %d\n", + addr_ai->ai_family); + ret = -EAFNOSUPPORT; + } + + freeaddrinfo(addr_ai); + freeaddrinfo(gw_ai); + return ret; +} diff --git a/libnetutils/ifc_utils.h b/libnetutils/ifc_utils.h deleted file mode 100644 index 49b8747..0000000 --- a/libnetutils/ifc_utils.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2008, The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _IFC_UTILS_H_ -#define _IFC_UTILS_H_ - -int ifc_init(void); - -int ifc_get_ifindex(const char *name, int *if_indexp); -int ifc_get_hwaddr(const char *name, void *ptr); - -int ifc_up(const char *name); -int ifc_down(const char *name); - -int ifc_set_addr(const char *name, unsigned addr); -int ifc_set_mask(const char *name, unsigned mask); - -int ifc_create_default_route(const char *name, unsigned addr); - -int ifc_get_info(const char *name, unsigned *addr, unsigned *mask, unsigned *flags); - -#endif diff --git a/libpixelflinger/Android.mk b/libpixelflinger/Android.mk index 6491d24..ed2ab5e 100644 --- a/libpixelflinger/Android.mk +++ b/libpixelflinger/Android.mk @@ -2,17 +2,6 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) # -# ARMv6 specific objects -# - -ifeq ($(TARGET_ARCH),arm) -LOCAL_ASFLAGS := -march=armv6 -LOCAL_SRC_FILES := rotate90CW_4x4_16v6.S -LOCAL_MODULE := libpixelflinger_armv6 -include $(BUILD_STATIC_LIBRARY) -endif - -# # C/C++ and ARMv5 objects # @@ -77,10 +66,6 @@ ifneq ($(BUILD_TINY_ANDROID),true) LOCAL_SHARED_LIBRARIES += libhardware_legacy LOCAL_CFLAGS += -DWITH_LIB_HARDWARE endif - -ifeq ($(TARGET_ARCH),arm) -LOCAL_WHOLE_STATIC_LIBRARIES := libpixelflinger_armv6 -endif include $(BUILD_SHARED_LIBRARY) # @@ -91,9 +76,6 @@ include $(CLEAR_VARS) LOCAL_MODULE:= libpixelflinger_static LOCAL_SRC_FILES := $(PIXELFLINGER_SRC_FILES) LOCAL_CFLAGS := $(PIXELFLINGER_CFLAGS) -ifeq ($(TARGET_ARCH),arm) -LOCAL_WHOLE_STATIC_LIBRARIES := libpixelflinger_armv6 -endif include $(BUILD_STATIC_LIBRARY) diff --git a/libpixelflinger/codeflinger/ARMAssembler.cpp b/libpixelflinger/codeflinger/ARMAssembler.cpp index d3720c3..fa9f1ad 100644 --- a/libpixelflinger/codeflinger/ARMAssembler.cpp +++ b/libpixelflinger/codeflinger/ARMAssembler.cpp @@ -433,6 +433,16 @@ void ARMAssembler::UXTB16(int cc, int Rd, int Rm, int rotate) { *mPC++ = (cc<<28) | 0x6CF0070 | (Rd<<12) | ((rotate >> 3) << 10) | Rm; } +#if 0 +#pragma mark - +#pragma mark Bit manipulation (ARMv7+ only)... +#endif + +// Bit manipulation (ARMv7+ only)... +void ARMAssembler::UBFX(int cc, int Rd, int Rn, int lsb, int width) +{ + *mPC++ = (cc<<28) | 0x7E00000 | ((width-1)<<16) | (Rd<<12) | (lsb<<7) | 0x50 | Rn; +} }; // namespace android diff --git a/libpixelflinger/codeflinger/ARMAssembler.h b/libpixelflinger/codeflinger/ARMAssembler.h index a667cb5..e7f038a 100644 --- a/libpixelflinger/codeflinger/ARMAssembler.h +++ b/libpixelflinger/codeflinger/ARMAssembler.h @@ -124,6 +124,7 @@ public: virtual void SMLAW(int cc, int y, int Rd, int Rm, int Rs, int Rn); virtual void UXTB16(int cc, int Rd, int Rm, int rotate); + virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width); private: ARMAssembler(const ARMAssembler& rhs); diff --git a/libpixelflinger/codeflinger/ARMAssemblerInterface.h b/libpixelflinger/codeflinger/ARMAssemblerInterface.h index ff6af2a..796342a 100644 --- a/libpixelflinger/codeflinger/ARMAssemblerInterface.h +++ b/libpixelflinger/codeflinger/ARMAssemblerInterface.h @@ -206,6 +206,9 @@ public: // byte/half word extract... virtual void UXTB16(int cc, int Rd, int Rm, int rotate) = 0; + // bit manipulation... + virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width) = 0; + // ----------------------------------------------------------------------- // convenience... // ----------------------------------------------------------------------- diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp index 7c422db..c57d7da 100644 --- a/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp +++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.cpp @@ -199,5 +199,9 @@ void ARMAssemblerProxy::UXTB16(int cc, int Rd, int Rm, int rotate) { mTarget->UXTB16(cc, Rd, Rm, rotate); } +void ARMAssemblerProxy::UBFX(int cc, int Rd, int Rn, int lsb, int width) { + mTarget->UBFX(cc, Rd, Rn, lsb, width); +} + }; // namespace android diff --git a/libpixelflinger/codeflinger/ARMAssemblerProxy.h b/libpixelflinger/codeflinger/ARMAssemblerProxy.h index 9134cce..8c7f270 100644 --- a/libpixelflinger/codeflinger/ARMAssemblerProxy.h +++ b/libpixelflinger/codeflinger/ARMAssemblerProxy.h @@ -115,6 +115,7 @@ public: int Rd, int Rm, int Rs, int Rn); virtual void UXTB16(int cc, int Rd, int Rm, int rotate); + virtual void UBFX(int cc, int Rd, int Rn, int lsb, int width); private: ARMAssemblerInterface* mTarget; diff --git a/libpixelflinger/codeflinger/disassem.c b/libpixelflinger/codeflinger/disassem.c index c17f3ec..aeb8034 100644 --- a/libpixelflinger/codeflinger/disassem.c +++ b/libpixelflinger/codeflinger/disassem.c @@ -81,6 +81,8 @@ * g - 2nd fp operand (register) (bits 16-18) * h - 3rd fp operand (register/immediate) (bits 0-4) * j - xtb rotate literal (bits 10-11) + * i - bfx lsb literal (bits 7-11) + * w - bfx width literal (bits 16-20) * b - branch address * t - thumb branch address (bits 24, 0-23) * k - breakpoint comment (bits 0-3, 8-19) @@ -124,6 +126,7 @@ static const struct arm32_insn arm32_i[] = { { 0x0fe000f0, 0x00a00090, "umlal", "Sdnms" }, { 0x0fe000f0, 0x00e00090, "smlal", "Sdnms" }, { 0x0fff03f0, 0x06cf0070, "uxtb16", "dmj" }, + { 0x0fe00070, 0x07e00050, "ubfx", "dmiw" }, { 0x0d700000, 0x04200000, "strt", "daW" }, { 0x0d700000, 0x04300000, "ldrt", "daW" }, { 0x0d700000, 0x04600000, "strbt", "daW" }, @@ -412,6 +415,14 @@ disasm(const disasm_interface_t *di, u_int loc, int altfmt) case 'j': di->di_printf("ror #%d", ((insn >> 10) & 3) << 3); break; + /* i - bfx lsb literal (bits 7-11) */ + case 'i': + di->di_printf("#%d", (insn >> 7) & 31); + break; + /* w - bfx width literal (bits 16-20) */ + case 'w': + di->di_printf("#%d", 1 + ((insn >> 16) & 31)); + break; /* b - branch address */ case 'b': branch = ((insn << 2) & 0x03ffffff); diff --git a/libpixelflinger/codeflinger/load_store.cpp b/libpixelflinger/codeflinger/load_store.cpp index 93c5825..ed20a00 100644 --- a/libpixelflinger/codeflinger/load_store.cpp +++ b/libpixelflinger/codeflinger/load_store.cpp @@ -18,9 +18,12 @@ #include <assert.h> #include <stdio.h> #include <cutils/log.h> - #include "codeflinger/GGLAssembler.h" +#ifdef __ARM_ARCH__ +#include <machine/cpu-features.h> +#endif + namespace android { // ---------------------------------------------------------------------------- @@ -110,6 +113,20 @@ void GGLAssembler::extract(integer_t& d, int s, int h, int l, int bits) assert(maskLen<=8); assert(h); +#if __ARM_ARCH__ >= 7 + const int mask = (1<<maskLen)-1; + if ((h == bits) && !l && (s != d.reg)) { + MOV(AL, 0, d.reg, s); // component = packed; + } else if ((h == bits) && l) { + MOV(AL, 0, d.reg, reg_imm(s, LSR, l)); // component = packed >> l; + } else if (!l && isValidImmediate(mask)) { + AND(AL, 0, d.reg, s, imm(mask)); // component = packed & mask; + } else if (!l && isValidImmediate(~mask)) { + BIC(AL, 0, d.reg, s, imm(~mask)); // component = packed & mask; + } else { + UBFX(AL, d.reg, s, l, maskLen); // component = (packed & mask) >> l; + } +#else if (h != bits) { const int mask = ((1<<maskLen)-1) << l; if (isValidImmediate(mask)) { @@ -132,6 +149,7 @@ void GGLAssembler::extract(integer_t& d, int s, int h, int l, int bits) if (s != d.reg) { MOV(AL, 0, d.reg, s); } +#endif d.s = maskLen; } diff --git a/libpixelflinger/col32cb16blend.S b/libpixelflinger/col32cb16blend.S index 1450bde..1831255 100644 --- a/libpixelflinger/col32cb16blend.S +++ b/libpixelflinger/col32cb16blend.S @@ -1,20 +1,19 @@ /* libs/pixelflinger/col32cb16blend.S -** -** (C) COPYRIGHT 2009 ARM Limited. -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -*/ + * + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ .text .align diff --git a/libpixelflinger/col32cb16blend_neon.S b/libpixelflinger/col32cb16blend_neon.S index 17b0d01..cbd54d1 100644 --- a/libpixelflinger/col32cb16blend_neon.S +++ b/libpixelflinger/col32cb16blend_neon.S @@ -1,20 +1,20 @@ /* libs/pixelflinger/col32cb16blend_neon.S -** -** (C) COPYRIGHT 2009 ARM Limited. -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -*/ + * + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .text .align diff --git a/libpixelflinger/raster.cpp b/libpixelflinger/raster.cpp index d751202..32b2a97 100644 --- a/libpixelflinger/raster.cpp +++ b/libpixelflinger/raster.cpp @@ -143,7 +143,7 @@ void ggl_copyPixels(void* con, GGLint xs, GGLint ys, using namespace android; -GGLint gglBitBlti(GGLContext* con, int tmu, GGLint crop[4], GGLint where[4]) +GGLint gglBitBlit(GGLContext* con, int tmu, GGLint crop[4], GGLint where[4]) { GGL_CONTEXT(c, (void*)con); diff --git a/libpixelflinger/scanline.cpp b/libpixelflinger/scanline.cpp index a2f43eb..931d648 100644 --- a/libpixelflinger/scanline.cpp +++ b/libpixelflinger/scanline.cpp @@ -1518,26 +1518,3 @@ void rect_memcpy(context_t* c, size_t yc) // ---------------------------------------------------------------------------- }; // namespace android -using namespace android; -extern "C" void ggl_test_codegen(uint32_t n, uint32_t p, uint32_t t0, uint32_t t1) -{ -#if ANDROID_ARM_CODEGEN - GGLContext* c; - gglInit(&c); - needs_t needs; - needs.n = n; - needs.p = p; - needs.t[0] = t0; - needs.t[1] = t1; - sp<ScanlineAssembly> a(new ScanlineAssembly(needs, ASSEMBLY_SCRATCH_SIZE)); - GGLAssembler assembler( new ARMAssembler(a) ); - int err = assembler.scanline(needs, (context_t*)c); - if (err != 0) { - printf("error %08x (%s)\n", err, strerror(-err)); - } - gglUninit(c); -#else - printf("This test runs only on ARM\n"); -#endif -} - diff --git a/libpixelflinger/tests/codegen/Android.mk b/libpixelflinger/tests/codegen/Android.mk index 1bc4214..aa320fc 100644 --- a/libpixelflinger/tests/codegen/Android.mk +++ b/libpixelflinger/tests/codegen/Android.mk @@ -2,12 +2,15 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - codegen.cpp + codegen.cpp.arm LOCAL_SHARED_LIBRARIES := \ libcutils \ libpixelflinger +LOCAL_C_INCLUDES := \ + system/core/libpixelflinger + LOCAL_MODULE:= test-opengl-codegen LOCAL_MODULE_TAGS := tests diff --git a/libpixelflinger/tests/codegen/codegen.cpp b/libpixelflinger/tests/codegen/codegen.cpp index 1865888..94e2481 100644 --- a/libpixelflinger/tests/codegen/codegen.cpp +++ b/libpixelflinger/tests/codegen/codegen.cpp @@ -1,9 +1,54 @@ #include <stdio.h> #include <stdint.h> -extern "C" void ggl_test_codegen( - uint32_t n, uint32_t p, uint32_t t0, uint32_t t1); +#include "private/pixelflinger/ggl_context.h" +#include "buffer.h" +#include "scanline.h" + +#include "codeflinger/CodeCache.h" +#include "codeflinger/GGLAssembler.h" +#include "codeflinger/ARMAssembler.h" + +#if defined(__arm__) +# define ANDROID_ARM_CODEGEN 1 +#else +# define ANDROID_ARM_CODEGEN 0 +#endif + +#define ASSEMBLY_SCRATCH_SIZE 2048 + +using namespace android; + +class ScanlineAssembly : public Assembly { + AssemblyKey<needs_t> mKey; +public: + ScanlineAssembly(needs_t needs, size_t size) + : Assembly(size), mKey(needs) { } + const AssemblyKey<needs_t>& key() const { return mKey; } +}; + +static void ggl_test_codegen(uint32_t n, uint32_t p, uint32_t t0, uint32_t t1) +{ +#if ANDROID_ARM_CODEGEN + GGLContext* c; + gglInit(&c); + needs_t needs; + needs.n = n; + needs.p = p; + needs.t[0] = t0; + needs.t[1] = t1; + sp<ScanlineAssembly> a(new ScanlineAssembly(needs, ASSEMBLY_SCRATCH_SIZE)); + GGLAssembler assembler( new ARMAssembler(a) ); + int err = assembler.scanline(needs, (context_t*)c); + if (err != 0) { + printf("error %08x (%s)\n", err, strerror(-err)); + } + gglUninit(c); +#else + printf("This test runs only on ARM\n"); +#endif +} int main(int argc, char** argv) { diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp index c2ba647..86c1f42 100644 --- a/libsysutils/src/NetlinkEvent.cpp +++ b/libsysutils/src/NetlinkEvent.cpp @@ -93,13 +93,11 @@ bool NetlinkEvent::decode(char *buffer, int size) { } const char *NetlinkEvent::findParam(const char *paramName) { - int i; - - for (i = 0; i < NL_PARAMS_MAX; i++) { - if (!mParams[i]) - break; - if (!strncmp(mParams[i], paramName, strlen(paramName))) - return &mParams[i][strlen(paramName) + 1]; + size_t len = strlen(paramName); + for (int i = 0; mParams[i] && i < NL_PARAMS_MAX; ++i) { + const char *ptr = mParams[i] + len; + if (!strncmp(mParams[i], paramName, len) && *ptr == '=') + return ++ptr; } SLOGE("NetlinkEvent::FindParam(): Parameter '%s' not found", paramName); diff --git a/mksh/Android.mk b/mksh/Android.mk new file mode 100644 index 0000000..e53b863 --- /dev/null +++ b/mksh/Android.mk @@ -0,0 +1,64 @@ +# Copyright © 2010 +# Thorsten Glaser <t.glaser@tarent.de> +# This file is provided under the same terms as mksh. + +LOCAL_PATH:= $(call my-dir) + + +# /system/etc/mkshrc + +include $(CLEAR_VARS) + +LOCAL_MODULE:= mkshrc +LOCAL_MODULE_TAGS:= shell_mksh +LOCAL_MODULE_CLASS:= ETC +LOCAL_MODULE_PATH:= $(TARGET_OUT)/etc +LOCAL_SRC_FILES:= $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + + +# /system/bin/mksh + +include $(CLEAR_VARS) + +LOCAL_MODULE:= mksh +LOCAL_MODULE_TAGS:= shell_mksh + +# mksh source files +LOCAL_SRC_FILES:= src/lalloc.c src/edit.c src/eval.c src/exec.c \ + src/expr.c src/funcs.c src/histrap.c src/jobs.c \ + src/lex.c src/main.c src/misc.c src/shf.c \ + src/syn.c src/tree.c src/var.c + +LOCAL_SYSTEM_SHARED_LIBRARIES:= libc + +LOCAL_C_INCLUDES:= $(LOCAL_PATH)/src +# additional flags first, then from Makefrag.inc: CFLAGS, CPPFLAGS +LOCAL_CFLAGS:= -DMKSH_DEFAULT_EXECSHELL=\"/system/bin/sh\" \ + -DMKSH_DEFAULT_TMPDIR=\"/sqlite_stmt_journals\" \ + -DMKSHRC_PATH=\"/system/etc/mkshrc\" \ + -fwrapv \ + -DMKSH_ASSUME_UTF8=0 -DMKSH_NOPWNAM \ + -D_GNU_SOURCE \ + -DHAVE_ATTRIBUTE_BOUNDED=0 -DHAVE_ATTRIBUTE_FORMAT=1 \ + -DHAVE_ATTRIBUTE_NONNULL=1 -DHAVE_ATTRIBUTE_NORETURN=1 \ + -DHAVE_ATTRIBUTE_UNUSED=1 -DHAVE_ATTRIBUTE_USED=1 \ + -DHAVE_SYS_PARAM_H=1 -DHAVE_SYS_MKDEV_H=0 \ + -DHAVE_SYS_MMAN_H=1 -DHAVE_SYS_SYSMACROS_H=1 \ + -DHAVE_GRP_H=1 -DHAVE_LIBGEN_H=1 -DHAVE_LIBUTIL_H=0 \ + -DHAVE_PATHS_H=1 -DHAVE_STDBOOL_H=1 -DHAVE_STDINT_H=1 \ + -DHAVE_STRINGS_H=1 -DHAVE_ULIMIT_H=0 -DHAVE_VALUES_H=0 \ + -DHAVE_CAN_INTTYPES=1 -DHAVE_CAN_UCBINTS=1 \ + -DHAVE_CAN_INT8TYPE=1 -DHAVE_CAN_UCBINT8=1 \ + -DHAVE_RLIM_T=1 -DHAVE_SIG_T=1 -DHAVE_SYS_SIGNAME=1 \ + -DHAVE_SYS_SIGLIST=1 -DHAVE_STRSIGNAL=0 \ + -DHAVE_GETRUSAGE=1 -DHAVE_KILLPG=1 -DHAVE_MKNOD=0 \ + -DHAVE_MKSTEMP=1 -DHAVE_NICE=1 -DHAVE_REVOKE=0 \ + -DHAVE_SETLOCALE_CTYPE=0 -DHAVE_LANGINFO_CODESET=0 \ + -DHAVE_SETMODE=1 -DHAVE_SETRESUGID=1 \ + -DHAVE_SETGROUPS=1 -DHAVE_STRCASESTR=1 \ + -DHAVE_STRLCPY=1 -DHAVE_FLOCK_DECL=1 \ + -DHAVE_REVOKE_DECL=1 -DHAVE_SYS_SIGLIST_DECL=1 \ + -DHAVE_PERSISTENT_HISTORY=0 + +include $(BUILD_EXECUTABLE) diff --git a/mksh/MODULE_LICENSE_BSD_LIKE b/mksh/MODULE_LICENSE_BSD_LIKE new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mksh/MODULE_LICENSE_BSD_LIKE diff --git a/mksh/NOTICE b/mksh/NOTICE new file mode 100644 index 0000000..350061f --- /dev/null +++ b/mksh/NOTICE @@ -0,0 +1,21 @@ +mksh is covered by The MirOS Licence: + +/*- + * Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un‐ + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person’s immediate fault when using the work as intended. + */ diff --git a/mksh/mkmf.sh b/mksh/mkmf.sh new file mode 100644 index 0000000..15d0432 --- /dev/null +++ b/mksh/mkmf.sh @@ -0,0 +1,124 @@ +# Copyright © 2010 +# Thorsten Glaser <t.glaser@tarent.de> +# This file is provided under the same terms as mksh. +#- +# Helper script to let src/Build.sh generate Makefrag.inc +# which we in turn use in the manual creation of Android.mk +# +# This script is supposed to be run from/inside AOSP by the +# porter of mksh to Android (and only manually). + +cd "$(dirname "$0")" +srcdir=$(pwd) +rm -rf tmp +mkdir tmp +cd ../../.. +aospdir=$(pwd) +cd $srcdir/tmp + +addvar() { + _vn=$1; shift + + eval $_vn=\"\$$_vn '$*"' +} + +CFLAGS= +CPPFLAGS= +LDFLAGS= +LIBS= + +# The definitions below were generated by examining the +# output of the following command: +# make showcommands out/target/product/generic/system/bin/mksh 2>&1 | tee log +# +# They are only used to let Build.sh find the compiler, header +# files, linker and libraries to generate Makefrag.inc (similar +# to what GNU autotools’ configure scripts do), and never used +# during the real build process. We need this to port mksh to +# the Android platform and it is crucial these are as close as +# possible to the values used later. (You also must example the +# results gathered from Makefrag.inc to see they are the same +# across all Android platforms, or add appropriate ifdefs.) +# Since we no longer use the NDK, the AOSP has to have been +# built before using this script (targetting generic/emulator). + +CC=$aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/arm-eabi-gcc +addvar CPPFLAGS -I$aospdir/system/core/include \ + -I$aospdir/hardware/libhardware/include \ + -I$aospdir/system/core/include \ + -I$aospdir/hardware/libhardware/include \ + -I$aospdir/hardware/libhardware_legacy/include \ + -I$aospdir/hardware/ril/include \ + -I$aospdir/dalvik/libnativehelper/include \ + -I$aospdir/frameworks/base/include \ + -I$aospdir/frameworks/base/opengl/include \ + -I$aospdir/external/skia/include \ + -I$aospdir/out/target/product/generic/obj/include \ + -I$aospdir/bionic/libc/arch-arm/include \ + -I$aospdir/bionic/libc/include \ + -I$aospdir/bionic/libstdc++/include \ + -I$aospdir/bionic/libc/kernel/common \ + -I$aospdir/bionic/libc/kernel/arch-arm \ + -I$aospdir/bionic/libm/include \ + -I$aospdir/bionic/libm/include/arch/arm \ + -I$aospdir/bionic/libthread_db/include \ + -D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ \ + -I$aospdir/system/core/include/arch/linux-arm/ \ + -include $aospdir/system/core/include/arch/linux-arm/AndroidConfig.h \ + -DANDROID -DNDEBUG -UDEBUG +addvar CFLAGS -fno-exceptions -Wno-multichar -msoft-float -fpic \ + -ffunction-sections -funwind-tables -fstack-protector -fno-short-enums \ + -march=armv5te -mtune=xscale -mthumb-interwork -fmessage-length=0 \ + -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Werror=return-type \ + -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point \ + -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once \ + -fgcse-after-reload -frerun-cse-after-loop -frename-registers -mthumb \ + -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64 +addvar LDFLAGS -nostdlib -Bdynamic -Wl,-T,$aospdir/build/core/armelf.x \ + -Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections \ + -Wl,-z,nocopyreloc -Wl,--no-undefined \ + $aospdir/out/target/product/generic/obj/lib/crtbegin_dynamic.o +addvar LIBS -L$aospdir/out/target/product/generic/obj/lib \ + -Wl,-rpath-link=$aospdir/out/target/product/generic/obj/lib -lc \ + $aospdir/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin/../lib/gcc/arm-eabi/4.4.0/interwork/libgcc.a \ + $aospdir/out/target/product/generic/obj/lib/crtend_android.o + + +### Override flags +# We don’t even *support* UTF-8 by default ☹ +addvar CPPFLAGS -DMKSH_ASSUME_UTF8=0 +# No getpwnam() calls (affects "cd ~username/" only) +addvar CPPFLAGS -DMKSH_NOPWNAM +# Compile an extra small mksh (optional) +#addvar CPPFLAGS -DMKSH_SMALL +# Leave out the ulimit builtin +#addvar CPPFLAGS -DMKSH_NO_LIMITS + +# Set target platform +TARGET_OS=Linux +# Building with -std=c99 or -std=gnu99 clashes with Bionic headers +HAVE_CAN_STDG99=0 +HAVE_CAN_STDC99=0 +export HAVE_CAN_STDG99 HAVE_CAN_STDC99 + +# Android-x86 does not have helper functions for ProPolice SSP +# and AOSP adds the flags by itself (same for warning flags) +HAVE_CAN_FNOSTRICTALIASING=0 +HAVE_CAN_FSTACKPROTECTORALL=0 +HAVE_CAN_WALL=0 +export HAVE_CAN_FNOSTRICTALIASING HAVE_CAN_FSTACKPROTECTORALL HAVE_CAN_WALL + +# disable the mknod(8) built-in to get rid of needing setmode.c +HAVE_MKNOD=0; export HAVE_MKNOD + +# even the idea of persistent history on a phone is funny +HAVE_PERSISTENT_HISTORY=0; export HAVE_PERSISTENT_HISTORY + +# ... and run it! +export CC CPPFLAGS CFLAGS LDFLAGS LIBS TARGET_OS +sh ../src/Build.sh -M +rv=$? +test x0 = x"$rv" && mv -f Makefrag.inc ../ +cd .. +rm -rf tmp +exit $rv diff --git a/mksh/mkshrc b/mksh/mkshrc new file mode 100644 index 0000000..0da5ea6 --- /dev/null +++ b/mksh/mkshrc @@ -0,0 +1,29 @@ +# Copyright (c) 2010 +# Thorsten Glaser <t.glaser@tarent.de> +# This file is provided under the same terms as mksh. +#- +# Minimal /system/etc/mkshrc for Android + +: ${TERM:=vt100} ${HOME:=/data} ${MKSH:=/system/bin/sh} ${HOSTNAME:=android} +: ${SHELL:=$MKSH} ${USER:=$(typeset x=$(id); x=${x#*\(}; print -r -- ${x%%\)*})} +if (( USER_ID )); then PS1='$'; else PS1='#'; fi +function precmd { + typeset e=$? + + (( e )) && print -n "$e|" +} +PS1='$(precmd)$USER@$HOSTNAME:${PWD:-?} '"$PS1 " +export HOME HOSTNAME MKSH PS1 SHELL TERM USER +alias l='ls' +alias la='l -a' +alias ll='l -l' +alias lo='l -a -l' + +for p in ~/.bin; do + [[ -d $p/. ]] || continue + [[ :$PATH: = *:$p:* ]] || PATH=$p:$PATH +done + +unset p + +: place customisations above this line diff --git a/mksh/src/00-NOTE.txt b/mksh/src/00-NOTE.txt new file mode 100644 index 0000000..b51d541 --- /dev/null +++ b/mksh/src/00-NOTE.txt @@ -0,0 +1,22 @@ +This is mksh from AnonCVS on 2010-08-24 with the +following files removed: +• Makefile + (not part of regular mksh releases anyway) +• dot.mkshrc + (not needed, we use our own for Android) +• mksh.1 + (manpage; also available from the web) +• setmode.c + (not needed, we don’t use the mknod builtin) +• strlcpy.c + (not needed, bionic provides this) + +The manual page can be downloaded as PDF (ISO A4 paper) from +https://www.mirbsd.org/MirOS/dist/mir/mksh/mksh.pdf or read +online at https://www.mirbsd.org/man1/mksh (HTML). + +The following changes are done to code in this subdirectory +at the moment: +• check.t main.sh: remove the 'stop' alias to 'kill -STOP' + since Android has a built in stop command that the alias + overrides, causing problems with testing tools
\ No newline at end of file diff --git a/mksh/src/Build.sh b/mksh/src/Build.sh new file mode 100644 index 0000000..c98b1ca --- /dev/null +++ b/mksh/src/Build.sh @@ -0,0 +1,1600 @@ +#!/bin/sh +srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.459 2010/08/24 15:46:06 tg Exp $' +#- +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +# Thorsten Glaser <tg@mirbsd.org> +# +# Provided that these terms and disclaimer and all copyright notices +# are retained or reproduced in an accompanying document, permission +# is granted to deal in this work without restriction, including un- +# limited rights to use, publicly perform, distribute, sell, modify, +# merge, give away, or sublicence. +# +# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to +# the utmost extent permitted by applicable law, neither express nor +# implied; without malicious intent or gross negligence. In no event +# may a licensor, author or contributor be held liable for indirect, +# direct, other damage, loss, or other issues arising in any way out +# of dealing in the work, even if advised of the possibility of such +# damage or existence of a defect, except proven that it results out +# of said person's immediate fault when using the work as intended. +#- +# People analysing the output must whitelist conftest.c for any kind +# of compiler warning checks (mirtoconf is by design not quiet). +# +# Environment used: CC CFLAGS CPPFLAGS LDFLAGS LIBS NOWARN NROFF +# TARGET_OS TARGET_OSREV +# Feature selectors: USE_PRINTF_BUILTIN +# CPPFLAGS recognised: MKSH_ASSUME_UTF8 MKSH_BINSHREDUCED MKSH_CLS_STRING +# MKSH_CONSERVATIVE_FDS MKSH_MIDNIGHTBSD01ASH_COMPAT +# MKSH_NOPWNAM MKSH_NO_LIMITS MKSH_SMALL MKSH_S_NOVI +# MKSH_UNEMPLOYED MKSH_DEFAULT_EXECSHELL MKSHRC_PATH +# MKSH_DEFAULT_TMPDIR MKSH_CLRTOEOL_STRING MKSH_A4PB + +LC_ALL=C +export LC_ALL + +v() { + $e "$*" + eval "$@" +} + +vv() { + _c=$1 + shift + $e "\$ $*" 2>&1 + eval "$@" >vv.out 2>&1 + sed "s^${_c} " <vv.out +} + +vq() { + eval "$@" +} + +rmf() { + for _f in "$@"; do + case ${_f} in + mksh.1) ;; + *) rm -f "${_f}" ;; + esac + done +} + +if test -d /usr/xpg4/bin/. >/dev/null 2>&1; then + # Solaris: some of the tools have weird behaviour, use portable ones + PATH=/usr/xpg4/bin:$PATH + export PATH +fi + +if test -n "${ZSH_VERSION+x}" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: +fi + +allu=QWERTYUIOPASDFGHJKLZXCVBNM +alll=qwertyuiopasdfghjklzxcvbnm +alln=0123456789 +alls=______________________________________________________________ +nl=' +' +tcfn=no +bi= +ui= +ao= +fx= +me=`basename "$0"` +orig_CFLAGS=$CFLAGS +phase=x +oldish_ed=stdout-ed,no-stderr-ed + +if test -t 1; then + bi='[1m' + ui='[4m' + ao='[0m' +fi + +upper() { + echo :"$@" | sed 's/^://' | tr $alll $allu +} + +# clean up after ac_testrun() +ac_testdone() { + eval HAVE_$fu=$fv + fr=no + test 0 = $fv || fr=yes + $e "$bi==> $fd...$ao $ui$fr$ao$fx" + fx= +} + +# ac_cache label: sets f, fu, fv?=0 +ac_cache() { + f=$1 + fu=`upper $f` + eval fv=\$HAVE_$fu + case $fv in + 0|1) + fx=' (cached)' + return 0 + ;; + esac + fv=0 + return 1 +} + +# ac_testinit label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput +# returns 1 if value was cached/implied, 0 otherwise: call ac_testdone +ac_testinit() { + if ac_cache $1; then + test x"$2" = x"!" && shift + test x"$2" = x"" || shift + fd=${3-$f} + ac_testdone + return 1 + fi + fc=0 + if test x"$2" = x""; then + ft=1 + else + if test x"$2" = x"!"; then + fc=1 + shift + fi + eval ft=\$HAVE_`upper $2` + shift + fi + fd=${3-$f} + if test $fc = "$ft"; then + fv=$2 + fx=' (implied)' + ac_testdone + return 1 + fi + $e ... $fd + return 0 +} + +# pipe .c | ac_test[n] [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput +ac_testn() { + if test x"$1" = x"!"; then + fr=1 + shift + else + fr=0 + fi + ac_testinit "$@" || return + cat >conftest.c + vv ']' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN conftest.c $LIBS $ccpr" + test $tcfn = no && test -f a.out && tcfn=a.out + test $tcfn = no && test -f a.exe && tcfn=a.exe + test $tcfn = no && test -f conftest && tcfn=conftest + if test -f $tcfn; then + test 1 = $fr || fv=1 + else + test 0 = $fr || fv=1 + fi + vscan= + if test $phase = u; then + test $ct = gcc && vscan='unrecogni[sz]ed' + test $ct = hpcc && vscan='unsupported' + test $ct = pcc && vscan='unsupported' + test $ct = sunpro && vscan='-e ignored -e turned.off' + fi + test -n "$vscan" && grep $vscan vv.out >/dev/null 2>&1 && fv=$fr + rmf conftest.c conftest.o ${tcfn}* vv.out + ac_testdone +} + +# ac_ifcpp cppexpr [!] label [!] checkif[!]0 [setlabelifcheckis[!]0] useroutput +ac_ifcpp() { + expr=$1; shift + ac_testn "$@" <<-EOF + int main(void) { return ( + #$expr + 0 + #else + /* force a failure: expr is false */ + thiswillneverbedefinedIhope() + #endif + ); } +EOF + test x"$1" = x"!" && shift + f=$1 + fu=`upper $f` + eval fv=\$HAVE_$fu + test x"$fv" = x"1" +} + +ac_cppflags() { + test x"$1" = x"" || fu=$1 + fv=$2 + test x"$2" = x"" && eval fv=\$HAVE_$fu + CPPFLAGS="$CPPFLAGS -DHAVE_$fu=$fv" +} + +ac_test() { + ac_testn "$@" + ac_cppflags +} + +# ac_flags [-] add varname flags [text] +ac_flags() { + if test x"$1" = x"-"; then + shift + hf=1 + else + hf=0 + fi + fa=$1 + vn=$2 + f=$3 + ft=$4 + test x"$ft" = x"" && ft="if $f can be used" + save_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS $f" + if test 1 = $hf; then + ac_testn can_$vn '' "$ft" + else + ac_testn can_$vn '' "$ft" <<-'EOF' + /* evil apo'stroph in comment test */ + int main(void) { return (0); } + EOF + fi + eval fv=\$HAVE_CAN_`upper $vn` + test 11 = $fa$fv || CFLAGS=$save_CFLAGS +} + +# ac_header [!] header [prereq ...] +ac_header() { + if test x"$1" = x"!"; then + na=1 + shift + else + na=0 + fi + hf=$1; shift + hv=`echo "$hf" | tr -d '\012\015' | tr -c $alll$allu$alln $alls` + for i + do + echo "#include <$i>" >>x + done + echo "#include <$hf>" >>x + echo 'int main(void) { return (0); }' >>x + ac_testn "$hv" "" "<$hf>" <x + rmf x + test 1 = $na || ac_cppflags +} + +addsrcs() { + if test x"$1" = x"!"; then + fr=0 + shift + else + fr=1 + fi + eval i=\$$1 + test $fr = "$i" && case " $SRCS " in + *\ $2\ *) ;; + *) SRCS="$SRCS $2" ;; + esac +} + + +if test -d mksh || test -d mksh.exe; then + echo "$me: Error: ./mksh is a directory!" >&2 + exit 1 +fi +rmf a.exe* a.out* conftest.c *core lft mksh* no *.bc *.ll *.o \ + Rebuild.sh signames.inc test.sh x vv.out + +curdir=`pwd` srcdir=`dirname "$0"` check_categories= +test -n "$dirname" || dirname=. +dstversion=`sed -n '/define MKSH_VERSION/s/^.*"\(.*\)".*$/\1/p' $srcdir/sh.h` + +e=echo +r=0 +eq=0 +pm=0 +cm=normal +optflags=-std-compile-opts +last= + +for i +do + case $last:$i in + c:combine|c:dragonegg|c:llvm) + cm=$i + last= + ;; + c:*) + echo "$me: Unknown option -c '$i'!" >&2 + exit 1 + ;; + o:*) + optflags=$i + last= + ;; + :-c) + last=c + ;; + :-combine) + cm=combine + echo "$me: Warning: '$i' is deprecated, use '-c combine' instead!" >&2 + ;; + :-g) + # checker, debug, valgrind build + CPPFLAGS="$CPPFLAGS -DDEBUG" + CFLAGS="$CFLAGS -g3 -fno-builtin" + ;; + :-j) + pm=1 + ;; + :-llvm) + cm=llvm + optflags=-std-compile-opts + echo "$me: Warning: '$i' is deprecated, use '-c llvm -O' instead!" >&2 + ;; + :-llvm=*) + cm=llvm + optflags=`echo "x$i" | sed 's/^x-llvm=//'` + echo "$me: Warning: '$i' is deprecated, use '-c llvm -o $llvm' instead!" >&2 + ;; + :-M) + cm=makefile + ;; + :-O) + optflags=-std-compile-opts + ;; + :-o) + last=o + ;; + :-Q) + eq=1 + ;; + :-r) + r=1 + ;; + :-v) + echo "Build.sh $srcversion" + echo "for mksh $dstversion" + exit 0 + ;; + :*) + echo "$me: Unknown option '$i'!" >&2 + exit 1 + ;; + *) + echo "$me: Unknown option -'$last' '$i'!" >&2 + exit 1 + ;; + esac +done +if test -n "$last"; then + echo "$me: Option -'$last' not followed by argument!" >&2 + exit 1 +fi + +SRCS="lalloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c" +SRCS="$SRCS jobs.c lex.c main.c misc.c shf.c syn.c tree.c var.c" + +if test x"$srcdir" = x"."; then + CPPFLAGS="-I. $CPPFLAGS" +else + CPPFLAGS="-I. -I'$srcdir' $CPPFLAGS" +fi + +test x"$TARGET_OS" = x"" && TARGET_OS=`uname -s 2>/dev/null || uname` +oswarn= +ccpc=-Wc, +ccpl=-Wl, +tsts= +ccpr='|| for _f in ${tcfn}*; do test x"${_f}" = x"mksh.1" || rm -f "${_f}"; done' + +# Configuration depending on OS revision, on OSes that need them +case $TARGET_OS in +QNX) + test x"$TARGET_OSREV" = x"" && TARGET_OSREV=`uname -r` + ;; +esac + +# Configuration depending on OS name +case $TARGET_OS in +AIX) + CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE" + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +BeOS) + oswarn=' and will currently not work' + ;; +BSD/OS) + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +CYGWIN*) + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +Darwin) + ;; +DragonFly) + ;; +FreeBSD) + ;; +GNU) + # define NO_PATH_MAX to use Hurd-only functions + CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -DNO_PATH_MAX" + ;; +GNU/kFreeBSD) + CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + ;; +Haiku) + CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8" + ;; +HP-UX) + ;; +Interix) + ccpc='-X ' + ccpl='-Y ' + CPPFLAGS="$CPPFLAGS -D_ALL_SOURCE" + : ${LIBS='-lcrypt'} + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +IRIX*) + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +Linux) + CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + : ${HAVE_REVOKE=0} + ;; +MidnightBSD) + ;; +Minix) + CPPFLAGS="$CPPFLAGS -DMKSH_UNEMPLOYED -DMKSH_CONSERVATIVE_FDS" + CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_POSIX_1_SOURCE=2 -D_MINIX" + oldish_ed=no-stderr-ed # /usr/bin/ed(!) is broken + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +MirBSD) + ;; +NetBSD) + ;; +OpenBSD) + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +OSF1) + HAVE_SIG_T=0 # incompatible + CPPFLAGS="$CPPFLAGS -D_OSF_SOURCE -D_POSIX_C_SOURCE=200112L" + CPPFLAGS="$CPPFLAGS -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED" + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +Plan9) + CPPFLAGS="$CPPFLAGS -D_POSIX_SOURCE -D_LIMITS_EXTENSION" + CPPFLAGS="$CPPFLAGS -D_BSD_EXTENSION -D_SUSV2_SOURCE" + oswarn=' and will currently not work' + CPPFLAGS="$CPPFLAGS -DMKSH_ASSUME_UTF8 -DMKSH_UNEMPLOYED" + ;; +PW32*) + HAVE_SIG_T=0 # incompatible + oswarn=' and will currently not work' + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +QNX) + CPPFLAGS="$CPPFLAGS -D__NO_EXT_QNX" + case $TARGET_OSREV in + [012345].*|6.[0123].*|6.4.[01]) + oldish_ed=no-stderr-ed # oldish /bin/ed is broken + ;; + esac + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +SunOS) + CPPFLAGS="$CPPFLAGS -D_BSD_SOURCE -D__EXTENSIONS__" + ;; +syllable) + CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" + oswarn=' and will currently not work' + ;; +ULTRIX) + : ${CC=cc -YPOSIX} + CPPFLAGS="$CPPFLAGS -Dssize_t=int" + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +UWIN*) + ccpc='-Yc,' + ccpl='-Yl,' + tsts=" 3<>/dev/tty" + oswarn="; it will compile, but the target" + oswarn="$oswarn${nl}platform itself is very flakey/unreliable" + : ${HAVE_SETLOCALE_CTYPE=0} + ;; +*) + oswarn='; it may or may not work' + ;; +esac + +: ${CC=cc} ${NROFF=nroff} +test 0 = $r && echo | $NROFF -v 2>&1 | grep GNU >/dev/null 2>&1 && \ + NROFF="$NROFF -c" + +# this aids me in tracing FTBFSen without access to the buildd +$e "Hi from$ao $bi$srcversion$ao on:" +case $TARGET_OS in +Darwin) + vv '|' "hwprefs machine_type os_type os_class >&2" + vv '|' "uname -a >&2" + ;; +IRIX*) + vv '|' "uname -a >&2" + vv '|' "hinv -v >&2" + ;; +OSF1) + vv '|' "uname -a >&2" + vv '|' "/usr/sbin/sizer -v >&2" + ;; +*) + vv '|' "uname -a >&2" + ;; +esac +test -z "$oswarn" || echo >&2 " +Warning: mksh has not yet been ported to or tested on your +operating system '$TARGET_OS'$oswarn. If you can provide +a shell account to the developer, this may improve; please +drop us a success or failure notice or even send in diffs. +" +$e "$bi$me: Building the MirBSD Korn Shell$ao $ui$dstversion$ao" + +# +# Begin of mirtoconf checks +# +$e $bi$me: Scanning for functions... please ignore any errors.$ao + +# +# Compiler: which one? +# +# notes: +# - ICC defines __GNUC__ too +# - GCC defines __hpux too +# - LLVM+clang defines __GNUC__ too +# - nwcc defines __GNUC__ too +CPP="$CC -E" +$e ... which compiler seems to be used +cat >conftest.c <<'EOF' +#if defined(__ICC) || defined(__INTEL_COMPILER) +ct=icc +#elif defined(__xlC__) || defined(__IBMC__) +ct=xlc +#elif defined(__SUNPRO_C) +ct=sunpro +#elif defined(__ACK__) +ct=ack +#elif defined(__BORLANDC__) +ct=bcc +#elif defined(__WATCOMC__) +ct=watcom +#elif defined(__MWERKS__) +ct=metrowerks +#elif defined(__HP_cc) +ct=hpcc +#elif defined(__DECC) || (defined(__osf__) && !defined(__GNUC__)) +ct=dec +#elif defined(__PGI) +ct=pgi +#elif defined(__DMC__) +ct=dmc +#elif defined(_MSC_VER) +ct=msc +#elif defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__) +ct=adsp +#elif defined(__IAR_SYSTEMS_ICC__) +ct=iar +#elif defined(SDCC) +ct=sdcc +#elif defined(__PCC__) +ct=pcc +#elif defined(__TenDRA__) +ct=tendra +#elif defined(__TINYC__) +ct=tcc +#elif defined(__llvm__) && defined(__clang__) +ct=clang +#elif defined(__NWCC__) +ct=nwcc +#elif defined(__GNUC__) +ct=gcc +#elif defined(_COMPILER_VERSION) +ct=mipspro +#elif defined(__sgi) +ct=mipspro +#elif defined(__hpux) || defined(__hpua) +ct=hpcc +#elif defined(__ultrix) +ct=ucode +#else +ct=unknown +#endif +EOF +ct=unknown +vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c | grep ct= | tr -d \\\\015 >x" +sed 's/^/[ /' x +eval `cat x` +rmf x vv.out +echo 'int main(void) { return (0); }' >conftest.c +case $ct in +ack) + # work around "the famous ACK const bug" + CPPFLAGS="-Dconst= $CPPFLAGS" + ;; +adsp) + echo >&2 'Warning: Analog Devices C++ compiler for Blackfin, TigerSHARC + and SHARC (21000) DSPs detected. This compiler has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +bcc) + echo >&2 "Warning: Borland C++ Builder detected. This compiler might + produce broken executables. Continue at your own risk, + please report success/failure to the developers." + ;; +clang) + # does not work with current "ccc" compiler driver + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version" + # this works, for now + vv '|' "${CLANG-clang} -version" + # ensure compiler and linker are in sync unless overridden + case $CCC_CC:$CCC_LD in + :*) ;; + *:) CCC_LD=$CCC_CC; export CCC_LD ;; + esac + ;; +dec) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V" + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS" + ;; +dmc) + echo >&2 "Warning: Digital Mars Compiler detected. When running under" + echo >&2 " UWIN, mksh tends to be unstable due to the limitations" + echo >&2 " of this platform. Continue at your own risk," + echo >&2 " please report success/failure to the developers." + ;; +gcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -v conftest.c $LIBS" + vv '|' 'echo `$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS \ + -dumpmachine` gcc`$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN \ + $LIBS -dumpversion`' + ;; +hpcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS" + ;; +iar) + echo >&2 'Warning: IAR Systems (http://www.iar.com) compiler for embedded + systems detected. This unsupported compiler has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +icc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V" + ;; +metrowerks) + echo >&2 'Warning: Metrowerks C compiler detected. This has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +mipspro) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version" + ;; +msc) + ccpr= # errorlevels are not reliable + case $TARGET_OS in + Interix) + if [[ -n $C89_COMPILER ]]; then + C89_COMPILER=`ntpath2posix -c "$C89_COMPILER"` + else + C89_COMPILER=CL.EXE + fi + if [[ -n $C89_LINKER ]]; then + C89_LINKER=`ntpath2posix -c "$C89_LINKER"` + else + C89_LINKER=LINK.EXE + fi + vv '|' "$C89_COMPILER /HELP >&2" + vv '|' "$C89_LINKER /LINK >&2" + ;; + esac + ;; +nwcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -version" + ;; +pcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v" + ;; +pgi) + echo >&2 'Warning: PGI detected. This unknown compiler has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +sdcc) + echo >&2 'Warning: sdcc (http://sdcc.sourceforge.net), the small devices + C compiler for embedded systems detected. This has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +sunpro) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -V conftest.c $LIBS" + ;; +tcc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -v" + ;; +tendra) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V 2>&1 | \ + fgrep -i -e version -e release" + ;; +ucode) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -V" + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN -Wl,-V conftest.c $LIBS" + ;; +watcom) + echo >&2 'Warning: Watcom C Compiler detected. This compiler has not yet + been tested for compatibility with mksh. Continue at your + own risk, please report success/failure to the developers.' + ;; +xlc) + vv '|' "$CC $CFLAGS $CPPFLAGS $LDFLAGS $NOWARN $LIBS -qversion=verbose" + vv '|' "ld -V" + ;; +*) + ct=unknown + ;; +esac +case $cm in +dragonegg|llvm) + vv '|' "llc -version" + ;; +esac +$e "$bi==> which compiler seems to be used...$ao $ui$ct$ao" +rmf conftest.c conftest.o conftest a.out* a.exe* vv.out + +# +# Compiler: works as-is, with -Wno-error and -Werror +# +save_NOWARN=$NOWARN +NOWARN= +DOWARN= +ac_flags 0 compiler_works '' 'if the compiler works' +test 1 = $HAVE_CAN_COMPILER_WORKS || exit 1 +HAVE_COMPILER_KNOWN=0 +test $ct = unknown || HAVE_COMPILER_KNOWN=1 +if ac_ifcpp 'if 0' compiler_fails '' \ + 'if the compiler does not fail correctly'; then + save_CFLAGS=$CFLAGS + : ${HAVE_CAN_DELEXE=x} + if test $ct = dmc; then + CFLAGS="$CFLAGS ${ccpl}/DELEXECUTABLE" + ac_testn can_delexe compiler_fails 0 'for the /DELEXECUTABLE linker option' <<-EOF + int main(void) { return (0); } + EOF + elif test $ct = dec; then + CFLAGS="$CFLAGS ${ccpl}-non_shared" + ac_testn can_delexe compiler_fails 0 'for the -non_shared linker option' <<-EOF + int main(void) { return (0); } + EOF + else + exit 1 + fi + test 1 = $HAVE_CAN_DELEXE || CFLAGS=$save_CFLAGS + ac_testn compiler_still_fails '' 'if the compiler still does not fail correctly' <<-EOF + EOF + test 1 = $HAVE_COMPILER_STILL_FAILS && exit 1 +fi +if ac_ifcpp 'ifdef __TINYC__' couldbe_tcc '!' compiler_known 0 \ + 'if this could be tcc'; then + ct=tcc + CPP='cpp -D__TINYC__' +fi + +if test $ct = sunpro; then + test x"$save_NOWARN" = x"" && save_NOWARN='-errwarn=%none' + ac_flags 0 errwarnnone "$save_NOWARN" + test 1 = $HAVE_CAN_ERRWARNNONE || save_NOWARN= + ac_flags 0 errwarnall "-errwarn=%all" + test 1 = $HAVE_CAN_ERRWARNALL && DOWARN="-errwarn=%all" +elif test $ct = hpcc; then + save_NOWARN= + DOWARN=+We +elif test $ct = mipspro; then + save_NOWARN= + DOWARN="-diag_error 1-10000" +elif test $ct = msc; then + save_NOWARN="${ccpc}/w" + DOWARN="${ccpc}/WX" +elif test $ct = dmc; then + save_NOWARN="${ccpc}-w" + DOWARN="${ccpc}-wx" +elif test $ct = bcc; then + save_NOWARN="${ccpc}-w" + DOWARN="${ccpc}-w!" +elif test $ct = dec; then + : -msg_* flags not used yet, or is -w2 correct? +elif test $ct = xlc; then + save_NOWARN=-qflag=i:e + DOWARN=-qflag=i:i +elif test $ct = tendra; then + save_NOWARN=-w +elif test $ct = ucode; then + save_NOWARN= + DOWARN=-w2 +else + test x"$save_NOWARN" = x"" && save_NOWARN=-Wno-error + ac_flags 0 wnoerror "$save_NOWARN" + test 1 = $HAVE_CAN_WNOERROR || save_NOWARN= + ac_flags 0 werror -Werror + test 1 = $HAVE_CAN_WERROR && DOWARN=-Werror +fi + +test $ct = icc && DOWARN="$DOWARN -wd1419" +NOWARN=$save_NOWARN + +# +# Compiler: extra flags (-O2 -f* -W* etc.) +# +i=`echo :"$orig_CFLAGS" | sed 's/^://' | tr -c -d $alll$allu$alln` +# optimisation: only if orig_CFLAGS is empty +test x"$i" = x"" && if test $ct = sunpro; then + cat >x <<-'EOF' + int main(void) { return (0); } + #define __IDSTRING_CONCAT(l,p) __LINTED__ ## l ## _ ## p + #define __IDSTRING_EXPAND(l,p) __IDSTRING_CONCAT(l,p) + #define pad void __IDSTRING_EXPAND(__LINE__,x)(void) { } + EOF + yes pad | head -n 256 >>x + ac_flags - 1 otwo -xO2 <x + rmf x +elif test $ct = hpcc; then + phase=u + ac_flags 1 otwo +O2 + phase=x +elif test $ct = xlc; then + ac_flags 1 othree "-O3 -qstrict" + test 1 = $HAVE_CAN_OTHREE || ac_flags 1 otwo -O2 +elif test $ct = tcc || test $ct = tendra; then + : no special optimisation +else + ac_flags 1 otwo -O2 + test 1 = $HAVE_CAN_OTWO || ac_flags 1 optimise -O +fi +# other flags: just add them if they are supported +i=0 +if test $ct = gcc; then + # The following tests run with -Werror (gcc only) if possible + NOWARN=$DOWARN; phase=u + ac_flags 1 fnostrictaliasing -fno-strict-aliasing + ac_flags 1 fstackprotectorall -fstack-protector-all + ac_flags 1 fwrapv -fwrapv + test $cm = combine && ac_flags 0 combine \ + '-fwhole-program --combine' \ + 'if gcc supports -fwhole-program --combine' + i=1 +elif test $ct = icc; then + ac_flags 1 fnobuiltinsetmode -fno-builtin-setmode + ac_flags 1 fnostrictaliasing -fno-strict-aliasing + ac_flags 1 fstacksecuritycheck -fstack-security-check + i=1 +elif test $ct = sunpro; then + phase=u + ac_flags 1 v -v + ac_flags 1 xc99 -xc99 'for support of ISO C99' + ac_flags 1 ipo -xipo 'for cross-module optimisation' + phase=x +elif test $ct = hpcc; then + phase=u + ac_flags 1 agcc -Agcc 'for support of GCC extensions' + ac_flags 1 ac99 -AC99 'for support of ISO C99' + phase=x +elif test $ct = dec; then + ac_flags 0 verb -verbose + ac_flags 1 rodata -readonly_strings +elif test $ct = dmc; then + ac_flags 1 decl "${ccpc}-r" 'for strict prototype checks' + ac_flags 1 schk "${ccpc}-s" 'for stack overflow checking' +elif test $ct = bcc; then + ac_flags 1 strpool "${ccpc}-d" 'if string pooling can be enabled' +elif test $ct = mipspro; then + ac_flags 1 xc99 -c99 'for support of ISO C99' + ac_flags 1 fullwarn -fullwarn 'for remark output support' +elif test $ct = msc; then + ac_flags 1 strpool "${ccpc}/GF" 'if string pooling can be enabled' + echo 'int main(void) { char test[64] = ""; return (*test); }' >x + ac_flags - 1 stackon "${ccpc}/GZ" 'if stack checks can be enabled' <x + ac_flags - 1 stckall "${ccpc}/Ge" 'stack checks for all functions' <x + ac_flags - 1 secuchk "${ccpc}/GS" 'for compiler security checks' <x + rmf x + ac_flags 1 wall "${ccpc}/Wall" 'to enable all warnings' + ac_flags 1 wp64 "${ccpc}/Wp64" 'to enable 64-bit warnings' +elif test $ct = xlc; then + ac_flags 1 x99 -qlanglvl=extc99 + test 1 = $HAVE_CAN_X99 || ac_flags 1 c99 -qlanglvl=stdc99 + ac_flags 1 rodata "-qro -qroconst -qroptr" + ac_flags 1 rtcheck -qcheck=all + ac_flags 1 rtchkc -qextchk + ac_flags 1 wformat "-qformat=all -qformat=nozln" + #ac_flags 1 wp64 -qwarn64 # too verbose for now +elif test $ct = tendra; then + ac_flags 0 ysystem -Ysystem + test 1 = $HAVE_CAN_YSYSTEM && CPPFLAGS="-Ysystem $CPPFLAGS" + ac_flags 1 extansi -Xa +elif test $ct = tcc; then + ac_flags 1 boundschk -b +elif test $ct = clang; then + i=1 +elif test $ct = nwcc; then + i=1 + #broken# ac_flags 1 ssp -stackprotect +fi +# flags common to a subset of compilers (run with -Werror on gcc) +if test 1 = $i; then + ac_flags 1 stdg99 -std=gnu99 'for support of ISO C99 + GCC extensions' + test 1 = $HAVE_CAN_STDG99 || \ + ac_flags 1 stdc99 -std=c99 'for support of ISO C99' + ac_flags 1 wall -Wall +fi +phase=x + +# The following tests run with -Werror or similar (all compilers) if possible +NOWARN=$DOWARN +test $ct = pcc && phase=u + +# +# Compiler: check for stuff that only generates warnings +# +ac_test attribute_bounded '' 'for __attribute__((bounded))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + #include <string.h> + #undef __attribute__ + int xcopy(const void *, void *, size_t) + __attribute__((bounded (buffer, 1, 3))) + __attribute__((bounded (buffer, 2, 3))); + int main(int ac, char *av[]) { return (xcopy(av[0], av[--ac], 1)); } + int xcopy(const void *s, void *d, size_t n) { + memmove(d, s, n); return ((int)n); + } + #endif +EOF +ac_test attribute_format '' 'for __attribute__((format))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + #include <stdio.h> + #undef __attribute__ + #undef printf + extern int printf(const char *format, ...) + __attribute__((format (printf, 1, 2))); + int main(int ac, char **av) { return (printf("%s%d", *av, ac)); } + #endif +EOF +ac_test attribute_nonnull '' 'for __attribute__((nonnull))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + int foo(char *s1, char *s2) __attribute__((nonnull)); + int bar(char *s1, char *s2) __attribute__((nonnull (1, 2))); + int baz(char *s) __attribute__((nonnull (1))); + int foo(char *s1, char *s2) { return (bar(s2, s1)); } + int bar(char *s1, char *s2) { return (baz(s1) - baz(s2)); } + int baz(char *s) { return (*s); } + int main(int ac, char **av) { return (ac == foo(av[0], av[ac-1])); } + #endif +EOF +ac_test attribute_noreturn '' 'for __attribute__((noreturn))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + #include <stdlib.h> + #undef __attribute__ + void fnord(void) __attribute__((noreturn)); + int main(void) { fnord(); } + void fnord(void) { exit(0); } + #endif +EOF +ac_test attribute_unused '' 'for __attribute__((unused))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + int main(int ac __attribute__((unused)), char **av + __attribute__((unused))) { return (0); } + #endif +EOF +ac_test attribute_used '' 'for __attribute__((used))' <<-'EOF' + #if defined(__GNUC__) && (__GNUC__ < 2) + /* force a failure: gcc 1.42 has a false positive here */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + static const char fnord[] __attribute__((used)) = "42"; + int main(void) { return (0); } + #endif +EOF + +# End of tests run with -Werror +NOWARN=$save_NOWARN +phase=x + +# +# mksh: flavours (full/small mksh, omit certain stuff) +# +if ac_ifcpp 'ifdef MKSH_SMALL' isset_MKSH_SMALL '' \ + "if a reduced-feature mksh is requested"; then + #XXX this sucks; fix it for *all* compilers + case $ct in + clang|icc|nwcc) + ac_flags 1 fnoinline -fno-inline + ;; + gcc) + NOWARN=$DOWARN; phase=u + ac_flags 1 fnoinline -fno-inline + NOWARN=$save_NOWARN; phase=x + ;; + sunpro) + ac_flags 1 fnoinline -xinline= + ;; + xlc) + ac_flags 1 fnoinline -qnoinline + ;; + esac + + : ${HAVE_MKNOD=0} + : ${HAVE_NICE=0} + : ${HAVE_REVOKE=0} + : ${HAVE_PERSISTENT_HISTORY=0} + check_categories=$check_categories,smksh + HAVE_ISSET_MKSH_CONSERVATIVE_FDS=1 # from sh.h +fi +ac_ifcpp 'ifdef MKSH_BINSHREDUCED' isset_MKSH_BINSHREDUCED '' \ + "if a reduced-feature sh is requested" && \ + check_categories=$check_categories,binsh +ac_ifcpp 'ifdef MKSH_UNEMPLOYED' isset_MKSH_UNEMPLOYED '' \ + "if mksh will be built without job control" && \ + check_categories=$check_categories,arge +ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \ + 'if the default UTF-8 mode is specified' && : ${HAVE_SETLOCALE_CTYPE=0} +ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \ + 'if traditional/conservative fd use is requested' && \ + check_categories=$check_categories,convfds + +# +# Environment: headers +# +ac_header sys/param.h +ac_header sys/mkdev.h sys/types.h +ac_header sys/mman.h sys/types.h +ac_header sys/sysmacros.h +ac_header grp.h sys/types.h +ac_header libgen.h +ac_header libutil.h sys/types.h +ac_header paths.h +ac_header stdbool.h +ac_header stdint.h stdarg.h +ac_header strings.h sys/types.h +ac_header ulimit.h sys/types.h +ac_header values.h + +# +# Environment: definitions +# +echo '#include <sys/types.h> +/* check that off_t can represent 2^63-1 correctly, thx FSF */ +#define LARGE_OFF_T (((off_t)1 << 62) - 1 + ((off_t)1 << 62)) +int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && + LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; +int main(void) { return (0); }' >lft.c +ac_testn can_lfs '' "for large file support" <lft.c +save_CPPFLAGS=$CPPFLAGS +CPPFLAGS="$CPPFLAGS -D_FILE_OFFSET_BITS=64" +ac_testn can_lfs_sus '!' can_lfs 0 "... with -D_FILE_OFFSET_BITS=64" <lft.c +if test 0 = $HAVE_CAN_LFS_SUS; then + CPPFLAGS="$save_CPPFLAGS -D_LARGE_FILES=1" + ac_testn can_lfs_aix '!' can_lfs 0 "... with -D_LARGE_FILES=1" <lft.c + test 1 = $HAVE_CAN_LFS_AIX || CPPFLAGS=$save_CPPFLAGS +fi +rmf lft* # end of large file support test + +# +# Environment: types +# +ac_test can_inttypes '!' stdint_h 1 "for standard 32-bit integer types" <<-'EOF' + #include <sys/types.h> + #include <stddef.h> + int main(int ac, char **av) { return ((uint32_t)(ptrdiff_t)*av + (int32_t)ac); } +EOF +ac_test can_ucbints '!' can_inttypes 1 "for UCB 32-bit integer types" <<-'EOF' + #include <sys/types.h> + #include <stddef.h> + int main(int ac, char **av) { return ((u_int32_t)(ptrdiff_t)*av + (int32_t)ac); } +EOF +ac_test can_int8type '!' stdint_h 1 "for standard 8-bit integer type" <<-'EOF' + #include <sys/types.h> + #include <stddef.h> + int main(int ac, char **av) { return ((uint8_t)(ptrdiff_t)av[ac]); } +EOF +ac_test can_ucbint8 '!' can_int8type 1 "for UCB 8-bit integer type" <<-'EOF' + #include <sys/types.h> + #include <stddef.h> + int main(int ac, char **av) { return ((u_int8_t)(ptrdiff_t)av[ac]); } +EOF + +ac_test rlim_t <<-'EOF' + #include <sys/types.h> + #include <sys/time.h> + #include <sys/resource.h> + #include <unistd.h> + int main(void) { return ((int)(rlim_t)0); } +EOF + +# only testn: added later below +ac_testn sig_t <<-'EOF' + #include <sys/types.h> + #include <signal.h> + #include <stddef.h> + int main(void) { return ((int)(ptrdiff_t)(sig_t)kill(0,0)); } +EOF + +ac_testn sighandler_t '!' sig_t 0 <<-'EOF' + #include <sys/types.h> + #include <signal.h> + #include <stddef.h> + int main(void) { return ((int)(ptrdiff_t)(sighandler_t)kill(0,0)); } +EOF +if test 1 = $HAVE_SIGHANDLER_T; then + CPPFLAGS="$CPPFLAGS -Dsig_t=sighandler_t" + HAVE_SIG_T=1 +fi + +ac_testn __sighandler_t '!' sig_t 0 <<-'EOF' + #include <sys/types.h> + #include <signal.h> + #include <stddef.h> + int main(void) { return ((int)(ptrdiff_t)(__sighandler_t)kill(0,0)); } +EOF +if test 1 = $HAVE___SIGHANDLER_T; then + CPPFLAGS="$CPPFLAGS -Dsig_t=__sighandler_t" + HAVE_SIG_T=1 +fi + +test 1 = $HAVE_SIG_T || CPPFLAGS="$CPPFLAGS -Dsig_t=nosig_t" +ac_cppflags SIG_T + +# +# Environment: signals +# +test x"NetBSD" = x"$TARGET_OS" && $e Ignore the compatibility warning. + +for what in name list; do + uwhat=`upper $what` + ac_testn sys_sig$what '' "the sys_sig${what}[] array" <<-EOF + extern const char *const sys_sig${what}[]; + int main(void) { return (sys_sig${what}[0][0]); } + EOF + ac_testn _sys_sig$what '!' sys_sig$what 0 "the _sys_sig${what}[] array" <<-EOF + extern const char *const _sys_sig${what}[]; + int main(void) { return (_sys_sig${what}[0][0]); } + EOF + if eval "test 1 = \$HAVE__SYS_SIG$uwhat"; then + CPPFLAGS="$CPPFLAGS -Dsys_sig$what=_sys_sig$what" + eval "HAVE_SYS_SIG$uwhat=1" + fi + ac_cppflags SYS_SIG$uwhat +done + +ac_test strsignal '!' sys_siglist 0 <<-'EOF' + #include <string.h> + #include <signal.h> + int main(void) { return (strsignal(1)[0]); } +EOF + +# +# Environment: library functions +# +ac_testn flock_ex '' 'flock and mmap' <<-'EOF' + #include <sys/types.h> + #include <sys/file.h> + #include <sys/mman.h> + #include <fcntl.h> + #include <stdlib.h> + int main(void) { return ((void *)mmap(NULL, (size_t)flock(0, LOCK_EX), + PROT_READ, MAP_PRIVATE, 0, (off_t)0) == (void *)NULL ? 1 : + munmap(NULL, 0)); } +EOF + +ac_test getrusage <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + int main(void) { + struct rusage ru; + return (getrusage(RUSAGE_SELF, &ru) + + getrusage(RUSAGE_CHILDREN, &ru)); + } +EOF + +ac_test killpg <<-'EOF' + #include <signal.h> + int main(int ac, char *av[]) { return (av[0][killpg(123, ac)]); } +EOF + +ac_test mknod '' 'if to use mknod(), makedev() and friends' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + int main(int ac, char *av[]) { + dev_t dv; + dv = makedev((unsigned int)ac, (unsigned int)av[0][0]); + return (mknod(av[0], (mode_t)0, dv) ? (int)major(dv) : + (int)minor(dv)); + } +EOF + +ac_test mkstemp <<-'EOF' + #include <stdlib.h> + #include <unistd.h> + int main(void) { char tmpl[] = "X"; return (mkstemp(tmpl)); } +EOF + +ac_test nice <<-'EOF' + #include <unistd.h> + int main(void) { return (nice(4)); } +EOF + +ac_test revoke <<-'EOF' + #include <sys/types.h> + #if HAVE_LIBUTIL_H + #include <libutil.h> + #endif + #include <unistd.h> + int main(int ac, char *av[]) { return (ac + revoke(av[0])); } +EOF + +ac_test setlocale_ctype '' 'setlocale(LC_CTYPE, "")' <<-'EOF' + #include <locale.h> + #include <stddef.h> + int main(void) { return ((int)(ptrdiff_t)(void *)setlocale(LC_CTYPE, "")); } +EOF + +ac_test langinfo_codeset setlocale_ctype 0 'nl_langinfo(CODESET)' <<-'EOF' + #include <langinfo.h> + #include <stddef.h> + int main(void) { return ((int)(ptrdiff_t)(void *)nl_langinfo(CODESET)); } +EOF + +ac_test setmode mknod 1 <<-'EOF' + /* XXX imake style */ + /* XXX conditions correct? */ + #if defined(__MSVCRT__) || defined(__CYGWIN__) + /* force a failure: Win32 setmode() is not what we want... */ + int main(void) { return (thiswillneverbedefinedIhope()); } + #else + #include <sys/types.h> + #include <unistd.h> + int main(int ac, char *av[]) { return (getmode(setmode(av[0]), + (mode_t)ac)); } + #endif +EOF + +ac_test setresugid <<-'EOF' + #include <sys/types.h> + #include <unistd.h> + int main(void) { setresuid(0,0,0); return (setresgid(0,0,0)); } +EOF + +ac_test setgroups setresugid 0 <<-'EOF' + #include <sys/types.h> + #if HAVE_GRP_H + #include <grp.h> + #endif + #include <unistd.h> + int main(void) { gid_t gid = 0; return (setgroups(0, &gid)); } +EOF + +ac_test strcasestr <<-'EOF' + #include <sys/types.h> + #include <stddef.h> + #include <string.h> + #if HAVE_STRINGS_H + #include <strings.h> + #endif + int main(int ac, char *av[]) { + return ((int)(ptrdiff_t)(void *)strcasestr(*av, av[ac])); + } +EOF + +ac_test strlcpy <<-'EOF' + #include <string.h> + int main(int ac, char *av[]) { return (strlcpy(*av, av[1], + (size_t)ac)); } +EOF + +# +# check headers for declarations +# +save_CC=$CC; save_LDFLAGS=$LDFLAGS; save_LIBS=$LIBS +CC="$CC -c -o $tcfn"; LDFLAGS=; LIBS= +ac_test '!' flock_decl flock_ex 1 'if flock() does not need to be declared' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + long flock(void); /* this clashes if defined before */ + int main(void) { return ((int)flock()); } +EOF +ac_test '!' revoke_decl revoke 1 'if revoke() does not need to be declared' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + long revoke(void); /* this clashes if defined before */ + int main(void) { return ((int)revoke()); } +EOF +ac_test sys_siglist_decl sys_siglist 1 'if sys_siglist[] does not need to be declared' <<-'EOF' + #define MKSH_INCLUDES_ONLY + #include "sh.h" + int main(void) { return (sys_siglist[0][0]); } +EOF +CC=$save_CC; LDFLAGS=$save_LDFLAGS; LIBS=$save_LIBS + +# +# other checks +# +fd='if to use persistent history' +ac_cache PERSISTENT_HISTORY || test 0 = $HAVE_FLOCK_EX || fv=1 +test 1 = $fv || check_categories=$check_categories,no-histfile +ac_testdone +ac_cppflags + +# +# Compiler: Praeprocessor (only if needed) +# +test 0 = $HAVE_SYS_SIGNAME && if ac_testinit cpp_dd '' \ + 'checking if the C Preprocessor supports -dD'; then + echo '#define foo bar' >conftest.c + vv ']' "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c >x" + grep '#define foo bar' x >/dev/null 2>&1 && fv=1 + rmf conftest.c x vv.out + ac_testdone +fi + +# +# End of mirtoconf checks +# +$e ... done. + +# Some operating systems have ancient versions of ed(1) writing +# the character count to standard output; cope for that +echo wq >x +ed x <x 2>/dev/null | grep 3 >/dev/null 2>&1 && \ + check_categories=$check_categories,$oldish_ed +rmf x vv.out + +if test 0 = $HAVE_SYS_SIGNAME; then + if test 1 = $HAVE_CPP_DD; then + $e Generating list of signal names... + else + $e No list of signal names available via cpp. Falling back... + fi + sigseen=: + echo '#include <signal.h> +#ifndef NSIG +#if defined(_NSIG) +#define NSIG _NSIG +#elif defined(SIGMAX) +#define NSIG (SIGMAX+1) +#endif +#endif +mksh_cfg: NSIG' >conftest.c + NSIG=`vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \ + grep mksh_cfg: | sed 's/^mksh_cfg:[ ]*\([0-9x ()+-]*\).*$/\1/'` + case $NSIG in + *[\ \(\)+-]*) NSIG=`awk "BEGIN { print $NSIG }"` ;; + esac + printf=printf + (printf hallo) >/dev/null 2>&1 || printf=echo + test $printf = echo || NSIG=`printf %d "$NSIG" 2>/dev/null` + $printf "NSIG=$NSIG ... " + sigs="ABRT ALRM BUS CHLD CLD CONT DIL EMT FPE HUP ILL INFO INT IO IOT" + sigs="$sigs KILL LOST PIPE PROF PWR QUIT RESV SAK SEGV STOP SYS TERM" + sigs="$sigs TRAP TSTP TTIN TTOU URG USR1 USR2 VTALRM WINCH XCPU XFSZ" + test 1 = $HAVE_CPP_DD && test $NSIG -gt 1 && sigs="$sigs "`vq \ + "$CPP $CFLAGS $CPPFLAGS $NOWARN -dD conftest.c" | \ + grep '[ ]SIG[A-Z0-9]*[ ]' | \ + sed 's/^\(.*[ ]SIG\)\([A-Z0-9]*\)\([ ].*\)$/\2/' | sort` + test $NSIG -gt 1 || sigs= + for name in $sigs; do + echo '#include <signal.h>' >conftest.c + echo mksh_cfg: SIG$name >>conftest.c + vq "$CPP $CFLAGS $CPPFLAGS $NOWARN conftest.c" | \ + grep mksh_cfg: | \ + sed 's/^mksh_cfg:[ ]*\([0-9x]*\).*$/\1:'$name/ + done | grep -v '^:' | while IFS=: read nr name; do + test $printf = echo || nr=`printf %d "$nr" 2>/dev/null` + test $nr -gt 0 && test $nr -le $NSIG || continue + case $sigseen in + *:$nr:*) ;; + *) echo " { \"$name\", $nr }," + sigseen=$sigseen$nr: + $printf "$name=$nr " >&2 + ;; + esac + done 2>&1 >signames.inc + rmf conftest.c + $e done. +fi + +addsrcs '!' HAVE_SETMODE setmode.c +addsrcs '!' HAVE_STRLCPY strlcpy.c +addsrcs USE_PRINTF_BUILTIN printf.c +test 1 = "$USE_PRINTF_BUILTIN" && CPPFLAGS="$CPPFLAGS -DMKSH_PRINTF_BUILTIN" +test 0 = "$HAVE_SETMODE" && CPPFLAGS="$CPPFLAGS -DHAVE_CONFIG_H -DCONFIG_H_FILENAME=\\\"sh.h\\\"" +test 1 = "$HAVE_CAN_VERB" && CFLAGS="$CFLAGS -verbose" + +$e $bi$me: Finished configuration testing, now producing output.$ao + +files= +objs= +sp= +case $curdir in +*\ *) echo "#!./mksh" >test.sh ;; +*) echo "#!$curdir/mksh" >test.sh ;; +esac +cat >>test.sh <<-EOF + LC_ALL=C PATH='$PATH'; export LC_ALL PATH + test -n "\$KSH_VERSION" || exit 1 + check_categories=$check_categories + print Testing mksh for conformance: + fgrep MirOS: '$srcdir/check.t' + fgrep MIRBSD '$srcdir/check.t' + print "This shell is actually:\\n\\t\$KSH_VERSION" + print 'test.sh built for mksh $dstversion' + cstr='\$os = defined \$^O ? \$^O : "unknown";' + cstr="\$cstr"'print \$os . ", Perl version " . \$];' + for perli in \$PERL perl5 perl no; do + [[ \$perli = no ]] && exit 1 + perlos=\$(\$perli -e "\$cstr") 2>&- || continue + print "Perl interpreter '\$perli' running on '\$perlos'" + [[ -n \$perlos ]] && break + done + exec \$perli '$srcdir/check.pl' -s '$srcdir/check.t' -p '$curdir/mksh' \${check_categories:+-C} \${check_categories#,} \$*$tsts +EOF +chmod 755 test.sh +test $HAVE_CAN_COMBINE$cm = 0combine && cm=normal +if test $cm = llvm; then + emitbc="-emit-llvm -c" +elif test $cm = dragonegg; then + emitbc="-S -flto" +else + emitbc=-c +fi +echo set -x >Rebuild.sh +for file in $SRCS; do + op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'` + test -f $file || file=$srcdir/$file + files="$files$sp$file" + sp=' ' + echo "$CC $CFLAGS $CPPFLAGS $emitbc $file || exit 1" >>Rebuild.sh + if test $cm = dragonegg; then + echo "mv ${op}s ${op}ll" >>Rebuild.sh + echo "llvm-as ${op}ll || exit 1" >>Rebuild.sh + objs="$objs$sp${op}bc" + else + objs="$objs$sp${op}o" + fi +done +case $cm in +dragonegg|llvm) + echo "rm -f mksh.s" >>Rebuild.sh + echo "llvm-link -o - $objs | opt $optflags | llc -o mksh.s" >>Rebuild.sh + lobjs=mksh.s + ;; +*) + lobjs=$objs + ;; +esac +case $tcfn in +a.exe) mkshexe=mksh.exe ;; +*) mkshexe=mksh ;; +esac +echo tcfn=$mkshexe >>Rebuild.sh +echo "$CC $CFLAGS $LDFLAGS -o \$tcfn $lobjs $LIBS $ccpr" >>Rebuild.sh +echo 'test -f $tcfn || exit 1; size $tcfn' >>Rebuild.sh +if test $cm = makefile; then + extras='emacsfn.h sh.h sh_flags.h var_spec.h' + test 0 = $HAVE_SYS_SIGNAME && extras="$extras signames.inc" + cat >Makefrag.inc <<EOF +# Makefile fragment for building mksh $dstversion + +PROG= $mkshexe +MAN= mksh.1 +SRCS= $SRCS +SRCS_FP= $files +OBJS_BP= $objs +INDSRCS= $extras +NONSRCS_INST= dot.mkshrc \$(MAN) +NONSRCS_NOINST= Build.sh Makefile Rebuild.sh check.pl check.t test.sh +CC= $CC +CFLAGS= $CFLAGS +CPPFLAGS= $CPPFLAGS +LDFLAGS= $LDFLAGS +LIBS= $LIBS + +# not BSD make only: +#VPATH= $srcdir +#all: \$(PROG) +#\$(PROG): \$(OBJS_BP) +# \$(CC) \$(CFLAGS) \$(LDFLAGS) -o \$@ \$(OBJS_BP) \$(LIBS) +#\$(OBJS_BP): \$(SRCS_FP) \$(NONSRCS) +#.c.o: +# \$(CC) \$(CFLAGS) \$(CPPFLAGS) -c \$< + +# for all make variants: +#REGRESS_FLAGS= -v +#regress: +# ./test.sh \$(REGRESS_FLAGS) + +# for BSD make only: +#.PATH: $srcdir +#.include <bsd.prog.mk> +EOF + $e + $e Generated Makefrag.inc successfully. + exit 0 +fi +if test $cm = combine; then + objs="-o $mkshexe" + for file in $SRCS; do + test -f $file || file=$srcdir/$file + objs="$objs $file" + done + emitbc="-fwhole-program --combine" + v "$CC $CFLAGS $CPPFLAGS $LDFLAGS $emitbc $objs $LIBS $ccpr" +elif test 1 = $pm; then + for file in $SRCS; do + test -f $file || file=$srcdir/$file + v "$CC $CFLAGS $CPPFLAGS $emitbc $file" & + done + wait +else + for file in $SRCS; do + test $cm = dragonegg && \ + op=`echo x"$file" | sed 's/^x\(.*\)\.c$/\1./'` + test -f $file || file=$srcdir/$file + v "$CC $CFLAGS $CPPFLAGS $emitbc $file" || exit 1 + if test $cm = dragonegg; then + v "mv ${op}s ${op}ll" + v "llvm-as ${op}ll" || exit 1 + fi + done +fi +case $cm in +dragonegg|llvm) + rmf mksh.s + v "llvm-link -o - $objs | opt $optflags | llc -o mksh.s" + ;; +esac +tcfn=$mkshexe +test $cm = combine || v "$CC $CFLAGS $LDFLAGS -o $tcfn $lobjs $LIBS $ccpr" +test -f $tcfn || exit 1 +test 1 = $r || v "$NROFF -mdoc <'$srcdir/mksh.1' >mksh.cat1" || \ + rmf mksh.cat1 +test 0 = $eq && v size $tcfn +i=install +test -f /usr/ucb/$i && i=/usr/ucb/$i +test 1 = $eq && e=: +$e +$e Installing the shell: +$e "# $i -c -s -o root -g bin -m 555 mksh /bin/mksh" +$e "# grep -x /bin/mksh /etc/shells >/dev/null || echo /bin/mksh >>/etc/shells" +$e "# $i -c -o root -g bin -m 444 dot.mkshrc /usr/share/doc/mksh/examples/" +$e +$e Installing the manual: +if test -f mksh.cat1; then + $e "# $i -c -o root -g bin -m 444 mksh.cat1" \ + "/usr/share/man/cat1/mksh.0" + $e or +fi +$e "# $i -c -o root -g bin -m 444 mksh.1 /usr/share/man/man1/mksh.1" +$e +$e Run the regression test suite: ./test.sh +$e Please also read the sample file dot.mkshrc and the fine manual. +exit 0 diff --git a/mksh/src/check.pl b/mksh/src/check.pl new file mode 100644 index 0000000..e793e95 --- /dev/null +++ b/mksh/src/check.pl @@ -0,0 +1,1241 @@ +# $MirOS: src/bin/mksh/check.pl,v 1.23 2009/06/10 18:12:43 tg Rel $ +# $OpenBSD: th,v 1.13 2006/05/18 21:27:23 miod Exp $ +#- +# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 +# Thorsten Glaser <tg@mirbsd.org> +# +# Provided that these terms and disclaimer and all copyright notices +# are retained or reproduced in an accompanying document, permission +# is granted to deal in this work without restriction, including un- +# limited rights to use, publicly perform, distribute, sell, modify, +# merge, give away, or sublicence. +# +# This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to +# the utmost extent permitted by applicable law, neither express nor +# implied; without malicious intent or gross negligence. In no event +# may a licensor, author or contributor be held liable for indirect, +# direct, other damage, loss, or other issues arising in any way out +# of dealing in the work, even if advised of the possibility of such +# damage or existence of a defect, except proven that it results out +# of said person's immediate fault when using the work as intended. +#- +# Example test: +# name: a-test +# description: +# a test to show how tests are done +# arguments: !-x!-f! +# stdin: +# echo -n * +# false +# expected-stdout: ! +# * +# expected-stderr: +# + echo -n * +# + false +# expected-exit: 1 +# --- +# This runs the test-program (eg, mksh) with the arguments -x and -f, +# standard input is a file containing "echo hi*\nfalse\n". The program +# is expected to produce "hi*" (no trailing newline) on standard output, +# "+ echo hi*\n+false\n" on standard error, and an exit code of 1. +# +# +# Format of test files: +# - blank lines and lines starting with # are ignored +# - a test file contains a series of tests +# - a test is a series of tag:value pairs ended with a "---" line +# (leading/trailing spaces are stripped from the first line of value) +# - test tags are: +# Tag Flag Description +# ----- ---- ----------- +# name r The name of the test; should be unique +# description m What test does +# arguments M Arguments to pass to the program; +# default is no arguments. +# script m Value is written to a file which +# is passed as an argument to the program +# (after the arguments arguments) +# stdin m Value is written to a file which is +# used as standard-input for the program; +# default is to use /dev/null. +# perl-setup m Value is a perl script which is executed +# just before the test is run. Try to +# avoid using this... +# perl-cleanup m Value is a perl script which is executed +# just after the test is run. Try to +# avoid using this... +# env-setup M Value is a list of NAME=VALUE elements +# which are put in the environment before +# the test is run. If the =VALUE is +# missing, NAME is removed from the +# environment. Programs are run with +# the following minimal environment: +# HOME, LD_LIBRARY_PATH, LOCPATH, +# LOGNAME, PATH, SHELL, USER +# (values taken from the environment of +# the test harness). +# ENV is set to /nonexistant. +# __progname is set to the -p argument. +# __perlname is set to $^X (perlexe). +# file-setup mps Used to create files, directories +# and symlinks. First word is either +# file, dir or symlink; second word is +# permissions; this is followed by a +# quoted word that is the name of the +# file; the end-quote should be followed +# by a newline, then the file data +# (if any). The first word may be +# preceded by a ! to strip the trailing +# newline in a symlink. +# file-result mps Used to verify a file, symlink or +# directory is created correctly. +# The first word is either +# file, dir or symlink; second word is +# expected permissions; third word +# is user-id; fourth is group-id; +# fifth is "exact" or "pattern" +# indicating whether the file contents +# which follow is to be matched exactly +# or if it is a regular expression. +# The fifth argument is the quoted name +# of the file that should be created. +# The end-quote should be followed +# by a newline, then the file data +# (if any). The first word may be +# preceded by a ! to strip the trailing +# newline in the file contents. +# The permissions, user and group fields +# may be * meaning accept any value. +# time-limit Time limit - the program is sent a +# SIGKILL N seconds. Default is no +# limit. +# expected-fail 'yes' if the test is expected to fail. +# expected-exit expected exit code. Can be a number, +# or a C expression using the variables +# e, s and w (exit code, termination +# signal, and status code). +# expected-stdout m What the test should generate on stdout; +# default is to expect no output. +# expected-stdout-pattern m A perl pattern which matches the +# expected output. +# expected-stderr m What the test should generate on stderr; +# default is to expect no output. +# expected-stderr-pattern m A perl pattern which matches the +# expected standard error. +# category m Specify a comma separated list of +# 'categories' of program that the test +# is to be run for. A category can be +# negated by prefixing the name with a !. +# The idea is that some tests in a +# test suite may apply to a particular +# program version and shouldn't be run +# on other versions. The category(s) of +# the program being tested can be +# specified on the command line. +# One category os:XXX is predefined +# (XXX is the operating system name, +# eg, linux, dec_osf). +# Flag meanings: +# r tag is required (eg, a test must have a name tag). +# m value can be multiple lines. Lines must be prefixed with +# a tab. If the value part of the initial tag:value line is +# - empty: the initial blank line is stripped. +# - a lone !: the last newline in the value is stripped; +# M value can be multiple lines (prefixed by a tab) and consists +# of multiple fields, delimited by a field separator character. +# The value must start and end with the f-s-c. +# p tag takes parameters (used with m). +# s tag can be used several times. + +use POSIX qw(EINTR); +use Getopt::Std; +use Config; + +$os = defined $^O ? $^O : 'unknown'; + +($prog = $0) =~ s#.*/##; + +$Usage = <<EOF ; +Usage: $prog [-s test-set] [-C category] [-p prog] [-v] [-e e=v] name ... + -p p Use p as the program to test + -C c Specify the comma separated list of categories the program + belongs to (see category field). + -s s Read tests from file s; if s is a directory, it is recursively + scaned for test files (which end in .t). + -t t Use t as default time limit for tests (default is unlimited) + -P program (-p) string has multiple words, and the program is in + the path (kludge option) + -v Verbose mode: print reason test failed. + -e e=v Set the environment variable e to v for all tests + (if no =v is given, the current value is used) + Only one -e option can be given at the moment, sadly. + name specifies the name of the test(s) to run; if none are + specified, all tests are run. +EOF + +# See comment above for flag meanings +%test_fields = ( + 'name', 'r', + 'description', 'm', + 'arguments', 'M', + 'script', 'm', + 'stdin', 'm', + 'perl-setup', 'm', + 'perl-cleanup', 'm', + 'env-setup', 'M', + 'file-setup', 'mps', + 'file-result', 'mps', + 'time-limit', '', + 'expected-fail', '', + 'expected-exit', '', + 'expected-stdout', 'm', + 'expected-stdout-pattern', 'm', + 'expected-stderr', 'm', + 'expected-stderr-pattern', 'm', + 'category', 'm', + ); +# Filled in by read_test() +%internal_test_fields = ( + ':full-name', 1, # file:name + ':long-name', 1, # dir/file:lineno:name + ); + +# Categories of the program under test. Provide the current +# os by default. +%categories = ( + "os:$os", '1' + ); + +$temps = "/tmp/rts$$"; +$tempi = "/tmp/rti$$"; +$tempo = "/tmp/rto$$"; +$tempe = "/tmp/rte$$"; +$tempdir = "/tmp/rtd$$"; + +$nfailed = 0; +$nxfailed = 0; +$npassed = 0; +$nxpassed = 0; + +%known_tests = (); + +if (!getopts('C:p:Ps:t:ve:')) { + print STDERR $Usage; + exit 1; +} + +die "$prog: no program specified (use -p)\n" if !defined $opt_p; +die "$prog: no test set specified (use -s)\n" if !defined $opt_s; +$test_prog = $opt_p; +$verbose = defined $opt_v && $opt_v; +$test_set = $opt_s; +if (defined $opt_t) { + die "$prog: bad -t argument (should be number > 0): $opt_t\n" + if $opt_t !~ /^\d+$/ || $opt_t <= 0; + $default_time_limit = $opt_t; +} +$program_kludge = defined $opt_P ? $opt_P : 0; + +if (defined $opt_C) { + foreach $c (split(',', $opt_C)) { + $c =~ s/\s+//; + die "$prog: categories can't be negated on the command line\n" + if ($c =~ /^!/); + $categories{$c} = 1; + } +} + +# Note which tests are to be run. +%do_test = (); +grep($do_test{$_} = 1, @ARGV); +$all_tests = @ARGV == 0; + +# Set up a very minimal environment +%new_env = (); +foreach $env (('HOME', 'LD_LIBRARY_PATH', 'LOCPATH', 'LOGNAME', + 'PATH', 'SHELL', 'USER')) { + $new_env{$env} = $ENV{$env} if defined $ENV{$env}; +} +$new_env{'ENV'} = '/nonexistant'; +if (($os eq 'VMS') || ($Config{perlpath} =~ m/$Config{_exe}$/i)) { + $new_env{'__perlname'} = $Config{perlpath}; +} else { + $new_env{'__perlname'} = $Config{perlpath} . $Config{_exe}; +} +if (defined $opt_e) { + # XXX need a way to allow many -e arguments... + if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) { + $new_env{$1} = $2 eq '' ? $ENV{$1} : $3; + } else { + die "$0: bad -e argument: $opt_e\n"; + } +} +%old_env = %ENV; + +die "$prog: couldn't make directory $tempdir - $!\n" if !mkdir($tempdir, 0777); + +chop($pwd = `pwd 2>/dev/null`); +die "$prog: couldn't get current working directory\n" if $pwd eq ''; +die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd); + +if (!$program_kludge) { + $test_prog = "$pwd/$test_prog" if substr($test_prog, 0, 1) ne '/'; + die "$prog: $test_prog is not executable - bye\n" + if (! -x $test_prog && $os ne 'os2'); +} + +@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP'); +@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs; +$child_kill_ok = 0; +$SIG{'ALRM'} = 'catch_sigalrm'; + +$| = 1; + +if (-d $test_set) { + $file_prefix_skip = length($test_set) + 1; + $ret = &process_test_dir($test_set); +} else { + $file_prefix_skip = 0; + $ret = &process_test_file($test_set); +} +&cleanup_exit() if !defined $ret; + +$tot_failed = $nfailed + $nxfailed; +$tot_passed = $npassed + $nxpassed; +if ($tot_failed || $tot_passed) { + print "Total failed: $tot_failed"; + print " ($nxfailed unexpected)" if $nxfailed; + print " (as expected)" if $nfailed && !$nxfailed; + print "\nTotal passed: $tot_passed"; + print " ($nxpassed unexpected)" if $nxpassed; + print "\n"; +} + +&cleanup_exit('ok'); + +sub +cleanup_exit +{ + local($sig, $exitcode) = ('', 1); + + if ($_[0] eq 'ok') { + $exitcode = 0; + } elsif ($_[0] ne '') { + $sig = $_[0]; + } + + unlink($tempi, $tempo, $tempe, $temps); + &scrub_dir($tempdir) if defined $tempdir; + rmdir($tempdir) if defined $tempdir; + + if ($sig) { + $SIG{$sig} = 'DEFAULT'; + kill $sig, $$; + return; + } + exit $exitcode; +} + +sub +catch_sigalrm +{ + $SIG{'ALRM'} = 'catch_sigalrm'; + kill(9, $child_pid) if $child_kill_ok; + $child_killed = 1; +} + +sub +process_test_dir +{ + local($dir) = @_; + local($ret, $file); + local(@todo) = (); + + if (!opendir(DIR, $dir)) { + print STDERR "$prog: can't open directory $dir - $!\n"; + return undef; + } + while (defined ($file = readdir(DIR))) { + push(@todo, $file) if $file =~ /^[^.].*\.t$/; + } + closedir(DIR); + + foreach $file (@todo) { + $file = "$dir/$file"; + if (-d $file) { + $ret = &process_test_dir($file); + } elsif (-f _) { + $ret = &process_test_file($file); + } + last if !defined $ret; + } + + return $ret; +} + +sub +process_test_file +{ + local($file) = @_; + local($ret); + + if (!open(IN, $file)) { + print STDERR "$prog: can't open $file - $!\n"; + return undef; + } + binmode(IN); + while (1) { + $ret = &read_test($file, IN, *test); + last if !defined $ret || !$ret; + next if !$all_tests && !$do_test{$test{'name'}}; + next if !&category_check(*test); + $ret = &run_test(*test); + last if !defined $ret; + } + close(IN); + + return $ret; +} + +sub +run_test +{ + local(*test) = @_; + local($name) = $test{':full-name'}; + + if (defined $test{'stdin'}) { + return undef if !&write_file($tempi, $test{'stdin'}); + $ifile = $tempi; + } else { + $ifile = '/dev/null'; + } + + if (defined $test{'script'}) { + return undef if !&write_file($temps, $test{'script'}); + } + + return undef if !&scrub_dir($tempdir); + + if (!chdir($tempdir)) { + print STDERR "$prog: couldn't cd to $tempdir - $!\n"; + return undef; + } + + if (defined $test{'file-setup'}) { + local($i); + local($type, $perm, $rest, $c, $len, $name); + + for ($i = 0; $i < $test{'file-setup'}; $i++) { + $val = $test{"file-setup:$i"}; + + # format is: type perm "name" + ($type, $perm, $rest) = + split(' ', $val, 3); + $c = substr($rest, 0, 1); + $len = index($rest, $c, 1) - 1; + $name = substr($rest, 1, $len); + $rest = substr($rest, 2 + $len); + $perm = oct($perm) if $perm =~ /^\d+$/; + if ($type eq 'file') { + return undef if !&write_file($name, $rest); + if (!chmod($perm, $name)) { + print STDERR + "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n"; + return undef; + } + } elsif ($type eq 'dir') { + if (!mkdir($name, $perm)) { + print STDERR + "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n"; + return undef; + } + } elsif ($type eq 'symlink') { + local($oumask) = umask($perm); + local($ret) = symlink($rest, $name); + umask($oumask); + if (!$ret) { + print STDERR + "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n"; + return undef; + } + } + } + } + + if (defined $test{'perl-setup'}) { + eval $test{'perl-setup'}; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n"; + return undef; + } + } + + $pid = fork; + if (!defined $pid) { + print STDERR "$prog: can't fork - $!\n"; + return undef; + } + if (!$pid) { + @SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs; + $SIG{'ALRM'} = 'DEFAULT'; + if (defined $test{'env-setup'}) { + local($var, $val, $i); + + foreach $var (split(substr($test{'env-setup'}, 0, 1), + $test{'env-setup'})) + { + $i = index($var, '='); + next if $i == 0 || $var eq ''; + if ($i < 0) { + delete $new_env{$var}; + } else { + $new_env{substr($var, 0, $i)} = substr($var, $i + 1); + } + } + } + if (!open(STDIN, "< $ifile")) { + print STDERR "$prog: couldn't open $ifile in child - $!\n"; + kill('TERM', $$); + } + binmode(STDIN); + if (!open(STDOUT, "> $tempo")) { + print STDERR "$prog: couldn't open $tempo in child - $!\n"; + kill('TERM', $$); + } + binmode(STDOUT); + if (!open(STDERR, "> $tempe")) { + print STDOUT "$prog: couldn't open $tempe in child - $!\n"; + kill('TERM', $$); + } + binmode(STDERR); + if ($program_kludge) { + @argv = split(' ', $test_prog); + } else { + @argv = ($test_prog); + } + if (defined $test{'arguments'}) { + push(@argv, + split(substr($test{'arguments'}, 0, 1), + substr($test{'arguments'}, 1))); + } + push(@argv, $temps) if defined $test{'script'}; + + #XXX realpathise, use which/whence -p, or sth. like that + #XXX if !$program_kludge, we get by with not doing it for now tho + $new_env{'__progname'} = $argv[0]; + + # The following doesn't work with perl5... Need to do it explicitly - yuck. + #%ENV = %new_env; + foreach $k (keys(%ENV)) { + delete $ENV{$k}; + } + $ENV{$k} = $v while ($k,$v) = each %new_env; + + exec { $argv[0] } @argv; + print STDERR "$prog: couldn't execute $test_prog - $!\n"; + kill('TERM', $$); + exit(95); + } + $child_pid = $pid; + $child_killed = 0; + $child_kill_ok = 1; + alarm($test{'time-limit'}) if defined $test{'time-limit'}; + while (1) { + $xpid = waitpid($pid, 0); + $child_kill_ok = 0; + if ($xpid < 0) { + next if $! == EINTR; + print STDERR "$prog: error waiting for child - $!\n"; + return undef; + } + last; + } + $status = $?; + alarm(0) if defined $test{'time-limit'}; + + $failed = 0; + $why = ''; + + if ($child_killed) { + $failed = 1; + $why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n"; + } + + $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'}); + return undef if !defined $ret; + if (!$ret) { + local($expl); + + $failed = 1; + if (($status & 0xff) == 0x7f) { + $expl = "stopped"; + } elsif (($status & 0xff)) { + $expl = "signal " . ($status & 0x7f); + } else { + $expl = "exit-code " . (($status >> 8) & 0xff); + } + $why .= + "\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n"; + } + + $tmp = &check_output($test{'long-name'}, $tempo, 'stdout', + $test{'expected-stdout'}, $test{'expected-stdout-pattern'}); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + $tmp = &check_output($test{'long-name'}, $tempe, 'stderr', + $test{'expected-stderr'}, $test{'expected-stderr-pattern'}); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + $tmp = &check_file_result(*test); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + if (defined $test{'perl-cleanup'}) { + eval $test{'perl-cleanup'}; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n"; + return undef; + } + } + + if (!chdir($pwd)) { + print STDERR "$prog: couldn't cd to $pwd - $!\n"; + return undef; + } + + if ($failed) { + if (!$test{'expected-fail'}) { + print "FAIL $name\n"; + $nxfailed++; + } else { + print "fail $name (as expected)\n"; + $nfailed++; + } + $why = "\tDescription" + . &wrap_lines($test{'description'}, " (missing)\n") + . $why; + } elsif ($test{'expected-fail'}) { + print "PASS $name (unexpectedly)\n"; + $nxpassed++; + } else { + print "pass $name\n"; + $npassed++; + } + print $why if $verbose; + return 0; +} + +sub +category_check +{ + local(*test) = @_; + local($c); + + return 1 if (!defined $test{'category'}); + local($ok) = 0; + foreach $c (split(',', $test{'category'})) { + $c =~ s/\s+//; + if ($c =~ /^!/) { + $c = $'; + return 0 if (defined $categories{$c}); + $ok = 1; + } else { + $ok = 1 if (defined $categories{$c}); + } + } + return $ok; +} + +sub +scrub_dir +{ + local($dir) = @_; + local(@todo) = (); + local($file); + + if (!opendir(DIR, $dir)) { + print STDERR "$prog: couldn't open directory $dir - $!\n"; + return undef; + } + while (defined ($file = readdir(DIR))) { + push(@todo, $file) if $file ne '.' && $file ne '..'; + } + closedir(DIR); + foreach $file (@todo) { + $file = "$dir/$file"; + if (-d $file) { + return undef if !&scrub_dir($file); + if (!rmdir($file)) { + print STDERR "$prog: couldn't rmdir $file - $!\n"; + return undef; + } + } else { + if (!unlink($file)) { + print STDERR "$prog: couldn't unlink $file - $!\n"; + return undef; + } + } + } + return 1; +} + +sub +write_file +{ + local($file, $str) = @_; + + if (!open(TEMP, "> $file")) { + print STDERR "$prog: can't open $file - $!\n"; + return undef; + } + binmode(TEMP); + print TEMP $str; + if (!close(TEMP)) { + print STDERR "$prog: error writing $file - $!\n"; + return undef; + } + return 1; +} + +sub +check_output +{ + local($name, $file, $what, $expect, $expect_pat) = @_; + local($got) = ''; + local($why) = ''; + local($ret); + + if (!open(TEMP, "< $file")) { + print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n"; + return undef; + } + binmode(TEMP); + while (<TEMP>) { + $got .= $_; + } + close(TEMP); + return compare_output($name, $what, $expect, $expect_pat, $got); +} + +sub +compare_output +{ + local($name, $what, $expect, $expect_pat, $got) = @_; + local($why) = ''; + + if (defined $expect_pat) { + $_ = $got; + $ret = eval "$expect_pat"; + if ($@ ne '') { + print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n"; + return undef; + } + if (!$ret) { + $why = "\tunexpected $what - wanted pattern"; + $why .= &wrap_lines($expect_pat); + $why .= "\tgot"; + $why .= &wrap_lines($got); + } + } else { + $expect = '' if !defined $expect; + if ($got ne $expect) { + $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n"; + $why .= "\twanted"; + $why .= &wrap_lines($expect); + $why .= "\tgot"; + $why .= &wrap_lines($got); + } + } + return $why; +} + +sub +wrap_lines +{ + local($str, $empty) = @_; + local($nonl) = substr($str, -1, 1) ne "\n"; + + return (defined $empty ? $empty : " nothing\n") if $str eq ''; + substr($str, 0, 0) = ":\n"; + $str =~ s/\n/\n\t\t/g; + if ($nonl) { + $str .= "\n\t[incomplete last line]\n"; + } else { + chop($str); + chop($str); + } + return $str; +} + +sub +first_diff +{ + local($exp, $got) = @_; + local($lineno, $char) = (1, 1); + local($i, $exp_len, $got_len); + local($ce, $cg); + + $exp_len = length($exp); + $got_len = length($got); + if ($exp_len != $got_len) { + if ($exp_len < $got_len) { + if (substr($got, 0, $exp_len) eq $exp) { + return "got too much output"; + } + } elsif (substr($exp, 0, $got_len) eq $got) { + return "got too little output"; + } + } + for ($i = 0; $i < $exp_len; $i++) { + $ce = substr($exp, $i, 1); + $cg = substr($got, $i, 1); + last if $ce ne $cg; + $char++; + if ($ce eq "\n") { + $lineno++; + $char = 1; + } + } + return "first difference: line $lineno, char $char (wanted '" + . &format_char($ce) . "', got '" + . &format_char($cg) . "'"; +} + +sub +format_char +{ + local($ch, $s); + + $ch = ord($_[0]); + if ($ch == 10) { + return '\n'; + } elsif ($ch == 13) { + return '\r'; + } elsif ($ch == 8) { + return '\b'; + } elsif ($ch == 9) { + return '\t'; + } elsif ($ch > 127) { + $ch -= 127; + $s = "M-"; + } else { + $s = ''; + } + if ($ch < 32) { + $s .= '^'; + $ch += ord('@'); + } elsif ($ch == 127) { + return $s . "^?"; + } + return $s . sprintf("%c", $ch); +} + +sub +eval_exit +{ + local($name, $status, $expect) = @_; + local($expr); + local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f); + + $e = -1000 if $status & 0xff; + $s = -1000 if $s == 0x7f; + if (!defined $expect) { + $expr = '$w == 0'; + } elsif ($expect =~ /^(|-)\d+$/) { + $expr = "\$e == $expect"; + } else { + $expr = $expect; + $expr =~ s/\b([wse])\b/\$$1/g; + $expr =~ s/\b(SIG[A-Z0-9]+)\b/&$1/g; + } + $w = eval $expr; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n"; + return undef; + } + return $w; +} + +sub +read_test +{ + local($file, $in, *test) = @_; + local($field, $val, $flags, $do_chop, $need_redo, $start_lineno); + local(%cnt, $sfield); + + %test = (); + %cnt = (); + while (<$in>) { + next if /^\s*$/; + next if /^ *#/; + last if /^\s*---\s*$/; + $start_lineno = $. if !defined $start_lineno; + if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) { + print STDERR "$prog:$file:$.: unrecognised line\n"; + return undef; + } + ($field, $val) = ($1, $2); + $sfield = $field; + $flags = $test_fields{$field}; + if (!defined $flags) { + print STDERR "$prog:$file:$.: unrecognised field \"$field\"\n"; + return undef; + } + if ($flags =~ /s/) { + local($cnt) = $cnt{$field}++; + $test{$field} = $cnt{$field}; + $cnt = 0 if $cnt eq ''; + $sfield .= ":$cnt"; + } elsif (defined $test{$field}) { + print STDERR "$prog:$file:$.: multiple \"$field\" fields\n"; + return undef; + } + $do_chop = $flags !~ /m/; + $need_redo = 0; + if ($val eq '' || $val eq '!' || $flags =~ /p/) { + if ($flags =~ /[Mm]/) { + if ($flags =~ /p/) { + if ($val =~ /^!/) { + $do_chop = 1; + $val = $'; + } else { + $do_chop = 0; + } + if ($val eq '') { + print STDERR + "$prog:$file:$.: no parameters given for field \"$field\"\n"; + return undef; + } + } else { + if ($val eq '!') { + $do_chop = 1; + } + $val = ''; + } + while (<$in>) { + last if !/^\t/; + $val .= $'; + } + chop $val if $do_chop; + $do_chop = 1; + $need_redo = 1; + + # Syntax check on fields that can several instances + # (can give useful line numbers this way) + + if ($field eq 'file-setup') { + local($type, $perm, $rest, $c, $len, $name); + + # format is: type perm "name" + if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) { + print STDERR + "$prog:$file:$.: bad parameter line for file-setup field\n"; + return undef; + } + ($type, $perm, $rest) = ($1, $2, $3); + if ($type !~ /^(file|dir|symlink)$/) { + print STDERR + "$prog:$file:$.: bad file type for file-setup: $type\n"; + return undef; + } + if ($perm !~ /^\d+$/) { + print STDERR + "$prog:$file:$.: bad permissions for file-setup: $type\n"; + return undef; + } + $c = substr($rest, 0, 1); + if (($len = index($rest, $c, 1) - 1) <= 0) { + print STDERR + "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n"; + return undef; + } + $name = substr($rest, 1, $len); + if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { + # Note: this is not a security thing - just a sanity + # check - a test can still use symlinks to get at files + # outside the test directory. + print STDERR +"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n"; + return undef; + } + } + if ($field eq 'file-result') { + local($type, $perm, $uid, $gid, $matchType, + $rest, $c, $len, $name); + + # format is: type perm uid gid matchType "name" + if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) { + print STDERR + "$prog:$file:$.: bad parameter line for file-result field\n"; + return undef; + } + ($type, $perm, $uid, $gid, $matchType, $rest) + = ($1, $2, $3, $4, $5, $6); + if ($type !~ /^(file|dir|symlink)$/) { + print STDERR + "$prog:$file:$.: bad file type for file-result: $type\n"; + return undef; + } + if ($perm !~ /^\d+$/ && $perm ne '*') { + print STDERR + "$prog:$file:$.: bad permissions for file-result: $perm\n"; + return undef; + } + if ($uid !~ /^\d+$/ && $uid ne '*') { + print STDERR + "$prog:$file:$.: bad user-id for file-result: $uid\n"; + return undef; + } + if ($gid !~ /^\d+$/ && $gid ne '*') { + print STDERR + "$prog:$file:$.: bad group-id for file-result: $gid\n"; + return undef; + } + if ($matchType !~ /^(exact|pattern)$/) { + print STDERR + "$prog:$file:$.: bad match type for file-result: $matchType\n"; + return undef; + } + $c = substr($rest, 0, 1); + if (($len = index($rest, $c, 1) - 1) <= 0) { + print STDERR + "$prog:$file:$.: missing end quote for file name in file-result: $rest\n"; + return undef; + } + $name = substr($rest, 1, $len); + if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { + # Note: this is not a security thing - just a sanity + # check - a test can still use symlinks to get at files + # outside the test directory. + print STDERR +"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n"; + return undef; + } + } + } elsif ($val eq '') { + print STDERR + "$prog:$file:$.: no value given for field \"$field\"\n"; + return undef; + } + } + $val .= "\n" if !$do_chop; + $test{$sfield} = $val; + redo if $need_redo; + } + if ($_ eq '') { + if (%test) { + print STDERR + "$prog:$file:$start_lineno: end-of-file while reading test\n"; + return undef; + } + return 0; + } + + while (($field, $val) = each %test_fields) { + if ($val =~ /r/ && !defined $test{$field}) { + print STDERR + "$prog:$file:$start_lineno: required field \"$field\" missing\n"; + return undef; + } + } + + $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}"; + $test{':long-name'} = "$file:$start_lineno:$test{'name'}"; + + # Syntax check on specific fields + if (defined $test{'expected-fail'}) { + if ($test{'expected-fail'} !~ /^(yes|no)$/) { + print STDERR + "$prog:$test{':long-name'}: bad value for expected-fail field\n"; + return undef; + } + $test{'expected-fail'} = $1 eq 'yes'; + } else { + $test{'expected-fail'} = 0; + } + if (defined $test{'arguments'}) { + local($firstc) = substr($test{'arguments'}, 0, 1); + + if (substr($test{'arguments'}, -1, 1) ne $firstc) { + print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n"; + return undef; + } + } + if (defined $test{'env-setup'}) { + local($firstc) = substr($test{'env-setup'}, 0, 1); + + if (substr($test{'env-setup'}, -1, 1) ne $firstc) { + print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n"; + return undef; + } + } + if (defined $test{'expected-exit'}) { + local($val) = $test{'expected-exit'}; + + if ($val =~ /^(|-)\d+$/) { + if ($val < 0 || $val > 255) { + print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n"; + return undef; + } + } elsif ($val !~ /^([\s<>+-=*%\/&|!()]|\b[wse]\b|\bSIG[A-Z0-9]+\b)+$/) { + print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n"; + return undef; + } + } else { + $test{'expected-exit'} = 0; + } + if (defined $test{'expected-stdout'} + && defined $test{'expected-stdout-pattern'}) + { + print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n"; + return undef; + } + if (defined $test{'expected-stderr'} + && defined $test{'expected-stderr-pattern'}) + { + print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n"; + return undef; + } + if (defined $test{'time-limit'}) { + if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) { + print STDERR + "$prog:$test{':long-name'}: bad value for time-limit field\n"; + return undef; + } + } elsif (defined $default_time_limit) { + $test{'time-limit'} = $default_time_limit; + } + + if (defined $known_tests{$test{'name'}}) { + print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n"; + } + $known_tests{$test{'name'}} = 1; + + return 1; +} + +sub +tty_msg +{ + local($msg) = @_; + + open(TTY, "> /dev/tty") || return 0; + print TTY $msg; + close(TTY); + return 1; +} + +sub +never_called_funcs +{ + return 0; + &tty_msg("hi\n"); + &never_called_funcs(); + &catch_sigalrm(); + $old_env{'foo'} = 'bar'; + $internal_test_fields{'foo'} = 'bar'; +} + +sub +check_file_result +{ + local(*test) = @_; + + return '' if (!defined $test{'file-result'}); + + local($why) = ''; + local($i); + local($type, $perm, $uid, $gid, $rest, $c, $len, $name); + local(@stbuf); + + for ($i = 0; $i < $test{'file-result'}; $i++) { + $val = $test{"file-result:$i"}; + + # format is: type perm "name" + ($type, $perm, $uid, $gid, $matchType, $rest) = + split(' ', $val, 6); + $c = substr($rest, 0, 1); + $len = index($rest, $c, 1) - 1; + $name = substr($rest, 1, $len); + $rest = substr($rest, 2 + $len); + $perm = oct($perm) if $perm =~ /^\d+$/; + + @stbuf = lstat($name); + if (!@stbuf) { + $why .= "\texpected $type \"$name\" not created\n"; + next; + } + if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) { + $why .= "\t$type \"$name\" has unexpected permissions\n"; + $why .= sprintf("\t\texpected 0%o, found 0%o\n", + $perm, $stbuf[2] & 07777); + } + if ($uid ne '*' && $stbuf[4] != $uid) { + $why .= "\t$type \"$name\" has unexpected user-id\n"; + $why .= sprintf("\t\texpected %d, found %d\n", + $uid, $stbuf[4]); + } + if ($gid ne '*' && $stbuf[5] != $gid) { + $why .= "\t$type \"$name\" has unexpected group-id\n"; + $why .= sprintf("\t\texpected %d, found %d\n", + $gid, $stbuf[5]); + } + + if ($type eq 'file') { + if (-l _ || ! -f _) { + $why .= "\t$type \"$name\" is not a regular file\n"; + } else { + local $tmp = &check_output($test{'long-name'}, $name, + "$type contents in \"$name\"", + $matchType eq 'exact' ? $rest : undef + $matchType eq 'pattern' ? $rest : undef); + return undef if (!defined $tmp); + $why .= $tmp; + } + } elsif ($type eq 'dir') { + if ($rest !~ /^\s*$/) { + print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n"; + return undef; + } + if (-l _ || ! -d _) { + $why .= "\t$type \"$name\" is not a directory\n"; + } + } elsif ($type eq 'symlink') { + if (!-l _) { + $why .= "\t$type \"$name\" is not a symlink\n"; + } else { + local $content = readlink($name); + if (!defined $content) { + print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n"; + return undef; + } + local $tmp = &compare_output($test{'long-name'}, + "$type contents in \"$name\"", + $matchType eq 'exact' ? $rest : undef + $matchType eq 'pattern' ? $rest : undef); + return undef if (!defined $tmp); + $why .= $tmp; + } + } + } + + return $why; +} + +sub +HELP_MESSAGE +{ + print STDERR $Usage; + exit 0; +} diff --git a/mksh/src/check.t b/mksh/src/check.t new file mode 100644 index 0000000..c8e8caf --- /dev/null +++ b/mksh/src/check.t @@ -0,0 +1,7443 @@ +# $MirOS: src/bin/mksh/check.t,v 1.388 2010/08/24 15:47:44 tg Exp $ +# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ +# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ +# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ +#- +# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 +# Thorsten Glaser <tg@mirbsd.org> +# +# Provided that these terms and disclaimer and all copyright notices +# are retained or reproduced in an accompanying document, permission +# is granted to deal in this work without restriction, including un‐ +# limited rights to use, publicly perform, distribute, sell, modify, +# merge, give away, or sublicence. +# +# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to +# the utmost extent permitted by applicable law, neither express nor +# implied; without malicious intent or gross negligence. In no event +# may a licensor, author or contributor be held liable for indirect, +# direct, other damage, loss, or other issues arising in any way out +# of dealing in the work, even if advised of the possibility of such +# damage or existence of a defect, except proven that it results out +# of said person’s immediate fault when using the work as intended. +#- +# You may also want to test IFS with the script at +# http://www.research.att.com/~gsf/public/ifs.sh + +expected-stdout: + @(#)MIRBSD KSH R39 2010/08/24 +description: + Check version of shell. +stdin: + echo $KSH_VERSION +name: KSH_VERSION +--- +name: selftest-1 +description: + Regression test self-testing +stdin: + echo ${foo:-baz} +expected-stdout: + baz +--- +name: selftest-2 +description: + Regression test self-testing +env-setup: !foo=bar! +stdin: + echo ${foo:-baz} +expected-stdout: + bar +--- +name: selftest-3 +description: + Regression test self-testing +env-setup: !ENV=fnord! +stdin: + echo "<$ENV>" +expected-stdout: + <fnord> +--- +name: selftest-env +description: + Just output the environment variables set (always fails) +category: disabled +stdin: + set +--- +name: alias-1 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias fooBar=fooBar + fooBar + exit 0 +expected-stderr-pattern: + /fooBar.*not found.*/ +--- +name: alias-2 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias fooBar=barFoo + alias barFoo=fooBar + fooBar + barFoo + exit 0 +expected-stderr-pattern: + /fooBar.*not found.*\n.*barFoo.*not found/ +--- +name: alias-3 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias Echo='echo ' + alias fooBar=barFoo + alias barFoo=fooBar + Echo fooBar + unalias barFoo + Echo fooBar +expected-stdout: + fooBar + barFoo +--- +name: alias-4 +description: + Check that alias expansion isn't done on keywords (in keyword + postitions). +stdin: + alias Echo='echo ' + alias while=While + while false; do echo hi ; done + Echo while +expected-stdout: + While +--- +name: alias-5 +description: + Check that alias expansion done after alias with trailing space. +stdin: + alias Echo='echo ' + alias foo='bar stuff ' + alias bar='Bar1 Bar2 ' + alias stuff='Stuff' + alias blah='Blah' + Echo foo blah +expected-stdout: + Bar1 Bar2 Stuff Blah +--- +name: alias-6 +description: + Check that alias expansion done after alias with trailing space. +stdin: + alias Echo='echo ' + alias foo='bar bar' + alias bar='Bar ' + alias blah=Blah + Echo foo blah +expected-stdout: + Bar Bar Blah +--- +name: alias-7 +description: + Check that alias expansion done after alias with trailing space + after a keyword. +stdin: + alias X='case ' + alias Y=Z + X Y in 'Y') echo is y ;; Z) echo is z ; esac +expected-stdout: + is z +--- +name: alias-8 +description: + Check that newlines in an alias don't cause the command to be lost. +stdin: + alias foo=' + + + echo hi + + + + echo there + + + ' + foo +expected-stdout: + hi + there +--- +name: alias-9 +description: + Check that recursion is detected/avoided in aliases. + This check fails for slow machines or Cygwin, raise + the time-limit clause (e.g. to 7) if this occurs. +time-limit: 3 +stdin: + echo -n >tf + alias ls=ls + ls + echo $(ls) + exit 0 +expected-stdout: + tf + tf +--- +name: alias-10 +description: + Check that recursion is detected/avoided in aliases. + Regression, introduced during an old bugfix. +stdin: + alias foo='print hello ' + alias bar='foo world' + echo $(bar) +expected-stdout: + hello world +--- +name: arith-lazy-1 +description: + Check that only one side of ternary operator is evaluated +stdin: + x=i+=2 + y=j+=2 + typeset -i i=1 j=1 + echo $((1 ? 20 : (x+=2))) + echo $i,$x + echo $((0 ? (y+=2) : 30)) + echo $j,$y +expected-stdout: + 20 + 1,i+=2 + 30 + 1,j+=2 +--- +name: arith-lazy-2 +description: + Check that assignments not done on non-evaluated side of ternary + operator +stdin: + x=i+=2 + y=j+=2 + typeset -i i=1 j=1 + echo $((1 ? 20 : (x+=2))) + echo $i,$x + echo $((0 ? (y+=2) : 30)) + echo $i,$y +expected-stdout: + 20 + 1,i+=2 + 30 + 1,j+=2 +--- +name: arith-lazy-3 +description: + Check that assignments not done on non-evaluated side of ternary + operator and this construct is parsed correctly (Debian #445651) +stdin: + x=4 + y=$((0 ? x=1 : 2)) + echo = $x $y = +expected-stdout: + = 4 2 = +--- +name: arith-ternary-prec-1 +description: + Check precedence of ternary operator vs assignment +stdin: + typeset -i x=2 + y=$((1 ? 20 : x+=2)) +expected-exit: e != 0 +expected-stderr-pattern: + /.*:.*1 \? 20 : x\+=2.*lvalue.*\n$/ +--- +name: arith-ternary-prec-2 +description: + Check precedence of ternary operator vs assignment +stdin: + typeset -i x=2 + echo $((0 ? x+=2 : 20)) +expected-stdout: + 20 +--- +name: arith-div-assoc-1 +description: + Check associativity of division operator +stdin: + echo $((20 / 2 / 2)) +expected-stdout: + 5 +--- +name: arith-assop-assoc-1 +description: + Check associativity of assignment-operator operator +stdin: + typeset -i i=1 j=2 k=3 + echo $((i += j += k)) + echo $i,$j,$k +expected-stdout: + 6 + 6,5,3 +--- +name: arith-unsigned-1 +description: + Check if unsigned arithmetics work +stdin: + # signed vs unsigned + echo x1 $((-1)) $((#-1)) + # calculating + typeset -i vs + typeset -Ui vu + vs=4123456789; vu=4123456789 + echo x2 $vs $vu + (( vs %= 2147483647 )) + (( vu %= 2147483647 )) + echo x3 $vs $vu + vs=4123456789; vu=4123456789 + (( # vs %= 2147483647 )) + (( # vu %= 2147483647 )) + echo x4 $vs $vu + # make sure the calculation does not change unsigned flag + vs=4123456789; vu=4123456789 + echo x5 $vs $vu + # short form + echo x6 $((# vs % 2147483647)) $((# vu % 2147483647)) + # array refs + set -A va + va[1975973142]=right + va[4123456789]=wrong + echo x7 ${va[#4123456789%2147483647]} +expected-stdout: + x1 -1 4294967295 + x2 -171510507 4123456789 + x3 -171510507 4123456789 + x4 1975973142 1975973142 + x5 -171510507 4123456789 + x6 1975973142 1975973142 + x7 right +--- +name: arith-limit32-1 +description: + Check if arithmetics are 32 bit +stdin: + # signed vs unsigned + echo x1 $((-1)) $((#-1)) + # calculating + typeset -i vs + typeset -Ui vu + vs=2147483647; vu=2147483647 + echo x2 $vs $vu + let vs++ vu++ + echo x3 $vs $vu + vs=4294967295; vu=4294967295 + echo x4 $vs $vu + let vs++ vu++ + echo x5 $vs $vu + let vs++ vu++ + echo x6 $vs $vu +expected-stdout: + x1 -1 4294967295 + x2 2147483647 2147483647 + x3 -2147483648 2147483648 + x4 -1 4294967295 + x5 0 0 + x6 1 1 +--- +name: bksl-nl-ign-1 +description: + Check that \newline is not collasped after # +stdin: + echo hi #there \ + echo folks +expected-stdout: + hi + folks +--- +name: bksl-nl-ign-2 +description: + Check that \newline is not collasped inside single quotes +stdin: + echo 'hi \ + there' + echo folks +expected-stdout: + hi \ + there + folks +--- +name: bksl-nl-ign-3 +description: + Check that \newline is not collasped inside single quotes +stdin: + cat << \EOF + hi \ + there + EOF +expected-stdout: + hi \ + there +--- +name: bksl-nl-ign-4 +description: + Check interaction of aliases, single quotes and here-documents + with backslash-newline + (don't know what POSIX has to say about this) +stdin: + a=2 + alias x='echo hi + cat << "EOF" + foo\ + bar + some' + x + more\ + stuff$a + EOF +expected-stdout: + hi + foo\ + bar + some + more\ + stuff$a +--- +name: bksl-nl-ign-5 +description: + Check what happens with backslash at end of input + (the old Bourne shell trashes them; so do we) +stdin: ! + echo `echo foo\\`bar + echo hi\ +expected-stdout: + foobar + hi +--- +# +# Places \newline should be collapsed +# +name: bksl-nl-1 +description: + Check that \newline is collasped before, in the middle of, and + after words +stdin: + \ + echo hi\ + There, \ + folks +expected-stdout: + hiThere, folks +--- +name: bksl-nl-2 +description: + Check that \newline is collasped in $ sequences + (ksh93 fails this) +stdin: + a=12 + ab=19 + echo $\ + a + echo $a\ + b + echo $\ + {a} + echo ${a\ + b} + echo ${ab\ + } +expected-stdout: + 12 + 19 + 12 + 19 + 19 +--- +name: bksl-nl-3 +description: + Check that \newline is collasped in $(..) and `...` sequences + (ksh93 fails this) +stdin: + echo $\ + (echo foobar1) + echo $(\ + echo foobar2) + echo $(echo foo\ + bar3) + echo $(echo foobar4\ + ) + echo ` + echo stuff1` + echo `echo st\ + uff2` +expected-stdout: + foobar1 + foobar2 + foobar3 + foobar4 + stuff1 + stuff2 +--- +name: bksl-nl-4 +description: + Check that \newline is collasped in $((..)) sequences + (ksh93 fails this) +stdin: + echo $\ + ((1+2)) + echo $(\ + (1+2+3)) + echo $((\ + 1+2+3+4)) + echo $((1+\ + 2+3+4+5)) + echo $((1+2+3+4+5+6)\ + ) +expected-stdout: + 3 + 6 + 10 + 15 + 21 +--- +name: bksl-nl-5 +description: + Check that \newline is collasped in double quoted strings +stdin: + echo "\ + hi" + echo "foo\ + bar" + echo "folks\ + " +expected-stdout: + hi + foobar + folks +--- +name: bksl-nl-6 +description: + Check that \newline is collasped in here document delimiters + (ksh93 fails second part of this) +stdin: + a=12 + cat << EO\ + F + a=$a + foo\ + bar + EOF + cat << E_O_F + foo + E_O_\ + F + echo done +expected-stdout: + a=12 + foobar + foo + done +--- +name: bksl-nl-7 +description: + Check that \newline is collasped in double-quoted here-document + delimiter. +stdin: + a=12 + cat << "EO\ + F" + a=$a + foo\ + bar + EOF + echo done +expected-stdout: + a=$a + foo\ + bar + done +--- +name: bksl-nl-8 +description: + Check that \newline is collasped in various 2+ character tokens + delimiter. + (ksh93 fails this) +stdin: + echo hi &\ + & echo there + echo foo |\ + | echo bar + cat <\ + < EOF + stuff + EOF + cat <\ + <\ + - EOF + more stuff + EOF + cat <<\ + EOF + abcdef + EOF + echo hi >\ + > /dev/null + echo $? + i=1 + case $i in + (\ + x|\ + 1\ + ) echo hi;\ + ; + (*) echo oops + esac +expected-stdout: + hi + there + foo + stuff + more stuff + abcdef + 0 + hi +--- +name: bksl-nl-9 +description: + Check that \ at the end of an alias is collapsed when followed + by a newline + (don't know what POSIX has to say about this) +stdin: + alias x='echo hi\' + x + echo there +expected-stdout: + hiecho there +--- +name: bksl-nl-10 +description: + Check that \newline in a keyword is collapsed +stdin: + i\ + f true; then\ + echo pass; el\ + se echo fail; fi +expected-stdout: + pass +--- +# +# Places \newline should be collapsed (ksh extensions) +# +name: bksl-nl-ksh-1 +description: + Check that \newline is collapsed in extended globbing + (ksh93 fails this) +stdin: + xxx=foo + case $xxx in + (f*\ + (\ + o\ + )\ + ) echo ok ;; + *) echo bad + esac +expected-stdout: + ok +--- +name: bksl-nl-ksh-2 +description: + Check that \newline is collapsed in ((...)) expressions + (ksh93 fails this) +stdin: + i=1 + (\ + (\ + i=i+2\ + )\ + ) + echo $i +expected-stdout: + 3 +--- +name: break-1 +description: + See if break breaks out of loops +stdin: + for i in a b c; do echo $i; break; echo bad-$i; done + echo end-1 + for i in a b c; do echo $i; break 1; echo bad-$i; done + echo end-2 + for i in a b c; do + for j in x y z; do + echo $i:$j + break + echo bad-$i + done + echo end-$i + done + echo end-3 +expected-stdout: + a + end-1 + a + end-2 + a:x + end-a + b:x + end-b + c:x + end-c + end-3 +--- +name: break-2 +description: + See if break breaks out of nested loops +stdin: + for i in a b c; do + for j in x y z; do + echo $i:$j + break 2 + echo bad-$i + done + echo end-$i + done + echo end +expected-stdout: + a:x + end +--- +name: break-3 +description: + What if break used outside of any loops + (ksh88,ksh93 don't print error messages here) +stdin: + break +expected-stderr-pattern: + /.*break.*/ +--- +name: break-4 +description: + What if break N used when only N-1 loops + (ksh88,ksh93 don't print error messages here) +stdin: + for i in a b c; do echo $i; break 2; echo bad-$i; done + echo end +expected-stdout: + a + end +expected-stderr-pattern: + /.*break.*/ +--- +name: break-5 +description: + Error if break argument isn't a number +stdin: + for i in a b c; do echo $i; break abc; echo more-$i; done + echo end +expected-stdout: + a +expected-exit: e != 0 +expected-stderr-pattern: + /.*break.*/ +--- +name: continue-1 +description: + See if continue continues loops +stdin: + for i in a b c; do echo $i; continue; echo bad-$i ; done + echo end-1 + for i in a b c; do echo $i; continue 1; echo bad-$i; done + echo end-2 + for i in a b c; do + for j in x y z; do + echo $i:$j + continue + echo bad-$i-$j + done + echo end-$i + done + echo end-3 +expected-stdout: + a + b + c + end-1 + a + b + c + end-2 + a:x + a:y + a:z + end-a + b:x + b:y + b:z + end-b + c:x + c:y + c:z + end-c + end-3 +--- +name: continue-2 +description: + See if continue breaks out of nested loops +stdin: + for i in a b c; do + for j in x y z; do + echo $i:$j + continue 2 + echo bad-$i-$j + done + echo end-$i + done + echo end +expected-stdout: + a:x + b:x + c:x + end +--- +name: continue-3 +description: + What if continue used outside of any loops + (ksh88,ksh93 don't print error messages here) +stdin: + continue +expected-stderr-pattern: + /.*continue.*/ +--- +name: continue-4 +description: + What if continue N used when only N-1 loops + (ksh88,ksh93 don't print error messages here) +stdin: + for i in a b c; do echo $i; continue 2; echo bad-$i; done + echo end +expected-stdout: + a + b + c + end +expected-stderr-pattern: + /.*continue.*/ +--- +name: continue-5 +description: + Error if continue argument isn't a number +stdin: + for i in a b c; do echo $i; continue abc; echo more-$i; done + echo end +expected-stdout: + a +expected-exit: e != 0 +expected-stderr-pattern: + /.*continue.*/ +--- +name: cd-history +description: + Test someone's CD history package (uses arrays) +stdin: + # go to known place before doing anything + cd / + + alias cd=_cd + function _cd + { + typeset -i cdlen i + typeset t + + if [ $# -eq 0 ] + then + set -- $HOME + fi + + if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists + then + typeset CDHIST + i=-1 + while read -r t # read directory history file + do + CDHIST[i=i+1]=$t + done <$CDHISTFILE + fi + + if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ] + then + _cdins # insert $PWD into cd history + fi + + cdlen=${#CDHIST[*]} # number of elements in history + + case "$@" in + -) # cd to new dir + if [ "$OLDPWD" = "" ] && ((cdlen>1)) + then + 'print' ${CDHIST[1]} + 'cd' ${CDHIST[1]} + _pwd + else + 'cd' $@ + _pwd + fi + ;; + -l) # print directory list + typeset -R3 num + ((i=cdlen)) + while (((i=i-1)>=0)) + do + num=$i + 'print' "$num ${CDHIST[i]}" + done + return + ;; + -[0-9]|-[0-9][0-9]) # cd to dir in list + if (((i=${1#-})<cdlen)) + then + 'print' ${CDHIST[i]} + 'cd' ${CDHIST[i]} + _pwd + else + 'cd' $@ + _pwd + fi + ;; + -*) # cd to matched dir in list + t=${1#-} + i=1 + while ((i<cdlen)) + do + case ${CDHIST[i]} in + *$t*) + 'print' ${CDHIST[i]} + 'cd' ${CDHIST[i]} + _pwd + break + ;; + esac + ((i=i+1)) + done + if ((i>=cdlen)) + then + 'cd' $@ + _pwd + fi + ;; + *) # cd to new dir + 'cd' $@ + _pwd + ;; + esac + + _cdins # insert $PWD into cd history + + if [ "$CDHISTFILE" ] + then + cdlen=${#CDHIST[*]} # number of elements in history + + i=0 + while ((i<cdlen)) + do + 'print' -r ${CDHIST[i]} # update directory history + ((i=i+1)) + done >$CDHISTFILE + fi + } + + function _cdins # insert $PWD into cd history + { # meant to be called only by _cd + typeset -i i + + ((i=0)) + while ((i<${#CDHIST[*]})) # see if dir is already in list + do + if [ "${CDHIST[$i]}" = "$PWD" ] + then + break + fi + ((i=i+1)) + done + + if ((i>22)) # limit max size of list + then + i=22 + fi + + while (((i=i-1)>=0)) # bump old dirs in list + do + CDHIST[i+1]=${CDHIST[i]} + done + + CDHIST[0]=$PWD # insert new directory in list + } + + + function _pwd + { + if [ -n "$ECD" ] + then + pwd 1>&6 + fi + } + # Start of test + cd /tmp + cd /bin + cd /etc + cd - + cd -2 + cd -l +expected-stdout: + /bin + /tmp + 3 / + 2 /etc + 1 /bin + 0 /tmp +--- +name: env-prompt +description: + Check that prompt not printed when processing ENV +env-setup: !ENV=./foo! +file-setup: file 644 "foo" + XXX=_ + PS1=X + false && echo hmmm +arguments: !-i! +stdin: + echo hi${XXX}there +expected-stdout: + hi_there +expected-stderr: ! + XX +--- +name: expand-ugly +description: + Check that weird ${foo+bar} constructs are parsed correctly +stdin: + (echo 1 ${IFS+'}'z}) 2>&- || echo failed in 1 + (echo 2 "${IFS+'}'z}") 2>&- || echo failed in 2 + (echo 3 "foo ${IFS+'bar} baz") 2>&- || echo failed in 3 + (echo -n '4 '; printf '%s\n' "foo ${IFS+"b c"} baz") 2>&- || echo failed in 4 + (echo -n '5 '; printf '%s\n' "foo ${IFS+b c} baz") 2>&- || echo failed in 5 + (echo 6 ${IFS+"}"z}) 2>&- || echo failed in 6 + (echo 7 "${IFS+"}"z}") 2>&- || echo failed in 7 + (echo 8 "${IFS+\"}\"z}") 2>&- || echo failed in 8 + (echo 9 "${IFS+\"\}\"z}") 2>&- || echo failed in 9 + (echo 10 foo ${IFS+'bar} baz'}) 2>&- || echo failed in 10 + (echo 11 "$(echo "${IFS+'}'z}")") 2>&- || echo failed in 11 + (echo 12 "$(echo ${IFS+'}'z})") 2>&- || echo failed in 12 + (echo 13 ${IFS+\}z}) 2>&- || echo failed in 13 + (echo 14 "${IFS+\}z}") 2>&- || echo failed in 14 + u=x; (echo -n '15 '; printf '<%s> ' "foo ${IFS+a"b$u{ {"{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz; echo .) 2>&- || echo failed in 15 + l=t; (echo 16 ${IFS+h`echo -n i ${IFS+$l}h`ere}) 2>&- || echo failed in 16 + l=t; (echo 17 ${IFS+h$(echo -n i ${IFS+$l}h)ere}) 2>&- || echo failed in 17 + l=t; (echo 18 "${IFS+h`echo -n i ${IFS+$l}h`ere}") 2>&- || echo failed in 18 + l=t; (echo 19 "${IFS+h$(echo -n i ${IFS+$l}h)ere}") 2>&- || echo failed in 19 + l=t; (echo 20 ${IFS+h`echo -n i "${IFS+$l}"h`ere}) 2>&- || echo failed in 20 + l=t; (echo 21 ${IFS+h$(echo -n i "${IFS+$l}"h)ere}) 2>&- || echo failed in 21 + l=t; (echo 22 "${IFS+h`echo -n i "${IFS+$l}"h`ere}") 2>&- || echo failed in 22 + l=t; (echo 23 "${IFS+h$(echo -n i "${IFS+$l}"h)ere}") 2>&- || echo failed in 23 + key=value; (echo -n '24 '; printf '%s\n' "${IFS+'$key'}") 2>&- || echo failed in 24 + key=value; (echo -n '25 '; printf '%s\n' "${IFS+"'$key'"}") 2>&- || echo failed in 25 # ksh93: “'$key'” + key=value; (echo -n '26 '; printf '%s\n' ${IFS+'$key'}) 2>&- || echo failed in 26 + key=value; (echo -n '27 '; printf '%s\n' ${IFS+"'$key'"}) 2>&- || echo failed in 27 + (echo -n '28 '; printf '%s\n' "${IFS+"'"x ~ x'}'x"'}"x}" #') 2>&- || echo failed in 28 + u=x; (echo -n '29 '; printf '<%s> ' foo ${IFS+a"b$u{ {"{ {\}b} c ${IFS+d{}} bar ${IFS-e{}} baz; echo .) 2>&- || echo failed in 29 + (echo -n '30 '; printf '<%s> ' ${IFS+foo 'b\ + ar' baz}; echo .) 2>&- || (echo failed in 30; echo failed in 31) + (echo -n '32 '; printf '<%s> ' ${IFS+foo "b\ + ar" baz}; echo .) 2>&- || echo failed in 32 + (echo -n '33 '; printf '<%s> ' "${IFS+foo 'b\ + ar' baz}"; echo .) 2>&- || echo failed in 33 + (echo -n '34 '; printf '<%s> ' "${IFS+foo "b\ + ar" baz}"; echo .) 2>&- || echo failed in 34 + (echo -n '35 '; printf '<%s> ' ${v=a\ b} x ${v=c\ d}; echo .) 2>&- || echo failed in 35 + (echo -n '36 '; printf '<%s> ' "${v=a\ b}" x "${v=c\ d}"; echo .) 2>&- || echo failed in 36 + (echo -n '37 '; printf '<%s> ' ${v-a\ b} x ${v-c\ d}; echo .) 2>&- || echo failed in 37 + (echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>&- || echo failed in 38 + foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>&- || echo failed in 39 + foo="a b c"; (echo -n '40 '; printf '<%s> ' "${foo#a}"; echo .) 2>&- || echo failed in 40 +expected-stdout: + 1 }z + 2 ''z} + 3 foo 'bar baz + 4 foo b c baz + 5 foo b c baz + 6 }z + 7 }z + 8 ""z} + 9 "}"z + 10 foo bar} baz + 11 ''z} + 12 }z + 13 }z + 14 }z + 15 <foo abx{ {{{}b c d{} bar> <}> <baz> . + 16 hi there + 17 hi there + 18 hi there + 19 hi there + 20 hi there + 21 hi there + 22 hi there + 23 hi there + 24 'value' + 25 'value' + 26 $key + 27 'value' + 28 'x ~ x''x}"x}" # + 29 <foo> <abx{ {{> <{}b> <c> <d{}> <bar> <}> <baz> . + 30 <foo> <b\ + ar> <baz> . + 32 <foo> <bar> <baz> . + 33 <foo 'bar' baz> . + 34 <foo bar baz> . + 35 <a> <b> <x> <a> <b> . + 36 <a\ b> <x> <a\ b> . + 37 <a b> <x> <c d> . + 38 xay / x'a'y . + 39 x' / x' . + 40 < b c> . +--- +name: expand-unglob-dblq +description: + Check that regular "${foo+bar}" constructs are parsed correctly +stdin: + u=x + tl_norm() { + v=$2 + test x"$v" = x"-" && unset v + (echo "$1 plus norm foo ${v+'bar'} baz") + (echo "$1 dash norm foo ${v-'bar'} baz") + (echo "$1 eqal norm foo ${v='bar'} baz") + (echo "$1 qstn norm foo ${v?'bar'} baz") 2>&- || \ + echo "$1 qstn norm -> error" + (echo "$1 PLUS norm foo ${v:+'bar'} baz") + (echo "$1 DASH norm foo ${v:-'bar'} baz") + (echo "$1 EQAL norm foo ${v:='bar'} baz") + (echo "$1 QSTN norm foo ${v:?'bar'} baz") 2>&- || \ + echo "$1 QSTN norm -> error" + } + tl_paren() { + v=$2 + test x"$v" = x"-" && unset v + (echo "$1 plus parn foo ${v+(bar)} baz") + (echo "$1 dash parn foo ${v-(bar)} baz") + (echo "$1 eqal parn foo ${v=(bar)} baz") + (echo "$1 qstn parn foo ${v?(bar)} baz") 2>&- || \ + echo "$1 qstn parn -> error" + (echo "$1 PLUS parn foo ${v:+(bar)} baz") + (echo "$1 DASH parn foo ${v:-(bar)} baz") + (echo "$1 EQAL parn foo ${v:=(bar)} baz") + (echo "$1 QSTN parn foo ${v:?(bar)} baz") 2>&- || \ + echo "$1 QSTN parn -> error" + } + tl_brace() { + v=$2 + test x"$v" = x"-" && unset v + (echo "$1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz") + (echo "$1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz") + (echo "$1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz") + (echo "$1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz") 2>&- || \ + echo "$1 qstn brac -> error" + (echo "$1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz") + (echo "$1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz") + (echo "$1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz") + (echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>&- || \ + echo "$1 QSTN brac -> error" + } + tl_norm 1 - + tl_norm 2 '' + tl_norm 3 x + tl_paren 4 - + tl_paren 5 '' + tl_paren 6 x + tl_brace 7 - + tl_brace 8 '' + tl_brace 9 x +expected-stdout: + 1 plus norm foo baz + 1 dash norm foo 'bar' baz + 1 eqal norm foo 'bar' baz + 1 qstn norm -> error + 1 PLUS norm foo baz + 1 DASH norm foo 'bar' baz + 1 EQAL norm foo 'bar' baz + 1 QSTN norm -> error + 2 plus norm foo 'bar' baz + 2 dash norm foo baz + 2 eqal norm foo baz + 2 qstn norm foo baz + 2 PLUS norm foo baz + 2 DASH norm foo 'bar' baz + 2 EQAL norm foo 'bar' baz + 2 QSTN norm -> error + 3 plus norm foo 'bar' baz + 3 dash norm foo x baz + 3 eqal norm foo x baz + 3 qstn norm foo x baz + 3 PLUS norm foo 'bar' baz + 3 DASH norm foo x baz + 3 EQAL norm foo x baz + 3 QSTN norm foo x baz + 4 plus parn foo baz + 4 dash parn foo (bar) baz + 4 eqal parn foo (bar) baz + 4 qstn parn -> error + 4 PLUS parn foo baz + 4 DASH parn foo (bar) baz + 4 EQAL parn foo (bar) baz + 4 QSTN parn -> error + 5 plus parn foo (bar) baz + 5 dash parn foo baz + 5 eqal parn foo baz + 5 qstn parn foo baz + 5 PLUS parn foo baz + 5 DASH parn foo (bar) baz + 5 EQAL parn foo (bar) baz + 5 QSTN parn -> error + 6 plus parn foo (bar) baz + 6 dash parn foo x baz + 6 eqal parn foo x baz + 6 qstn parn foo x baz + 6 PLUS parn foo (bar) baz + 6 DASH parn foo x baz + 6 EQAL parn foo x baz + 6 QSTN parn foo x baz + 7 plus brac foo c } baz + 7 dash brac foo ax{{{}b c d{} baz + 7 eqal brac foo ax{{{}b c ax{{{}b} baz + 7 qstn brac -> error + 7 PLUS brac foo c } baz + 7 DASH brac foo ax{{{}b c d{} baz + 7 EQAL brac foo ax{{{}b c ax{{{}b} baz + 7 QSTN brac -> error + 8 plus brac foo ax{{{}b c d{} baz + 8 dash brac foo c } baz + 8 eqal brac foo c } baz + 8 qstn brac foo c } baz + 8 PLUS brac foo c } baz + 8 DASH brac foo ax{{{}b c d{} baz + 8 EQAL brac foo ax{{{}b c ax{{{}b} baz + 8 QSTN brac -> error + 9 plus brac foo ax{{{}b c d{} baz + 9 dash brac foo x c x} baz + 9 eqal brac foo x c x} baz + 9 qstn brac foo x c x} baz + 9 PLUS brac foo ax{{{}b c d{} baz + 9 DASH brac foo x c x} baz + 9 EQAL brac foo x c x} baz + 9 QSTN brac foo x c x} baz +--- +name: expand-unglob-unq +description: + Check that regular ${foo+bar} constructs are parsed correctly +stdin: + u=x + tl_norm() { + v=$2 + test x"$v" = x"-" && unset v + (echo $1 plus norm foo ${v+'bar'} baz) + (echo $1 dash norm foo ${v-'bar'} baz) + (echo $1 eqal norm foo ${v='bar'} baz) + (echo $1 qstn norm foo ${v?'bar'} baz) 2>&- || \ + echo "$1 qstn norm -> error" + (echo $1 PLUS norm foo ${v:+'bar'} baz) + (echo $1 DASH norm foo ${v:-'bar'} baz) + (echo $1 EQAL norm foo ${v:='bar'} baz) + (echo $1 QSTN norm foo ${v:?'bar'} baz) 2>&- || \ + echo "$1 QSTN norm -> error" + } + tl_paren() { + v=$2 + test x"$v" = x"-" && unset v + (echo $1 plus parn foo ${v+\(bar')'} baz) + (echo $1 dash parn foo ${v-\(bar')'} baz) + (echo $1 eqal parn foo ${v=\(bar')'} baz) + (echo $1 qstn parn foo ${v?\(bar')'} baz) 2>&- || \ + echo "$1 qstn parn -> error" + (echo $1 PLUS parn foo ${v:+\(bar')'} baz) + (echo $1 DASH parn foo ${v:-\(bar')'} baz) + (echo $1 EQAL parn foo ${v:=\(bar')'} baz) + (echo $1 QSTN parn foo ${v:?\(bar')'} baz) 2>&- || \ + echo "$1 QSTN parn -> error" + } + tl_brace() { + v=$2 + test x"$v" = x"-" && unset v + (echo $1 plus brac foo ${v+a$u{{{\}b} c ${v+d{}} baz) + (echo $1 dash brac foo ${v-a$u{{{\}b} c ${v-d{}} baz) + (echo $1 eqal brac foo ${v=a$u{{{\}b} c ${v=d{}} baz) + (echo $1 qstn brac foo ${v?a$u{{{\}b} c ${v?d{}} baz) 2>&- || \ + echo "$1 qstn brac -> error" + (echo $1 PLUS brac foo ${v:+a$u{{{\}b} c ${v:+d{}} baz) + (echo $1 DASH brac foo ${v:-a$u{{{\}b} c ${v:-d{}} baz) + (echo $1 EQAL brac foo ${v:=a$u{{{\}b} c ${v:=d{}} baz) + (echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>&- || \ + echo "$1 QSTN brac -> error" + } + tl_norm 1 - + tl_norm 2 '' + tl_norm 3 x + tl_paren 4 - + tl_paren 5 '' + tl_paren 6 x + tl_brace 7 - + tl_brace 8 '' + tl_brace 9 x +expected-stdout: + 1 plus norm foo baz + 1 dash norm foo bar baz + 1 eqal norm foo bar baz + 1 qstn norm -> error + 1 PLUS norm foo baz + 1 DASH norm foo bar baz + 1 EQAL norm foo bar baz + 1 QSTN norm -> error + 2 plus norm foo bar baz + 2 dash norm foo baz + 2 eqal norm foo baz + 2 qstn norm foo baz + 2 PLUS norm foo baz + 2 DASH norm foo bar baz + 2 EQAL norm foo bar baz + 2 QSTN norm -> error + 3 plus norm foo bar baz + 3 dash norm foo x baz + 3 eqal norm foo x baz + 3 qstn norm foo x baz + 3 PLUS norm foo bar baz + 3 DASH norm foo x baz + 3 EQAL norm foo x baz + 3 QSTN norm foo x baz + 4 plus parn foo baz + 4 dash parn foo (bar) baz + 4 eqal parn foo (bar) baz + 4 qstn parn -> error + 4 PLUS parn foo baz + 4 DASH parn foo (bar) baz + 4 EQAL parn foo (bar) baz + 4 QSTN parn -> error + 5 plus parn foo (bar) baz + 5 dash parn foo baz + 5 eqal parn foo baz + 5 qstn parn foo baz + 5 PLUS parn foo baz + 5 DASH parn foo (bar) baz + 5 EQAL parn foo (bar) baz + 5 QSTN parn -> error + 6 plus parn foo (bar) baz + 6 dash parn foo x baz + 6 eqal parn foo x baz + 6 qstn parn foo x baz + 6 PLUS parn foo (bar) baz + 6 DASH parn foo x baz + 6 EQAL parn foo x baz + 6 QSTN parn foo x baz + 7 plus brac foo c } baz + 7 dash brac foo ax{{{}b c d{} baz + 7 eqal brac foo ax{{{}b c ax{{{}b} baz + 7 qstn brac -> error + 7 PLUS brac foo c } baz + 7 DASH brac foo ax{{{}b c d{} baz + 7 EQAL brac foo ax{{{}b c ax{{{}b} baz + 7 QSTN brac -> error + 8 plus brac foo ax{{{}b c d{} baz + 8 dash brac foo c } baz + 8 eqal brac foo c } baz + 8 qstn brac foo c } baz + 8 PLUS brac foo c } baz + 8 DASH brac foo ax{{{}b c d{} baz + 8 EQAL brac foo ax{{{}b c ax{{{}b} baz + 8 QSTN brac -> error + 9 plus brac foo ax{{{}b c d{} baz + 9 dash brac foo x c x} baz + 9 eqal brac foo x c x} baz + 9 qstn brac foo x c x} baz + 9 PLUS brac foo ax{{{}b c d{} baz + 9 DASH brac foo x c x} baz + 9 EQAL brac foo x c x} baz + 9 QSTN brac foo x c x} baz +--- +name: eglob-bad-1 +description: + Check that globbing isn't done when glob has syntax error +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" +stdin: + echo !([*)* + echo +(a|b[)* +expected-stdout: + !([*)* + +(a|b[)* +--- +name: eglob-bad-2 +description: + Check that globbing isn't done when glob has syntax error + (AT&T ksh fails this test) +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" +stdin: + echo [a*(]*)z +expected-stdout: + [a*(]*)z +--- +name: eglob-infinite-plus +description: + Check that shell doesn't go into infinite loop expanding +(...) + expressions. +file-setup: file 644 "abc" +time-limit: 3 +stdin: + echo +()c + echo +()x + echo +(*)c + echo +(*)x +expected-stdout: + +()c + +()x + abc + +(*)x +--- +name: eglob-subst-1 +description: + Check that eglobbing isn't done on substitution results +file-setup: file 644 "abc" +stdin: + x='@(*)' + echo $x +expected-stdout: + @(*) +--- +name: eglob-nomatch-1 +description: + Check that the pattern doesn't match +stdin: + echo 1: no-file+(a|b)stuff + echo 2: no-file+(a*(c)|b)stuff + echo 3: no-file+((((c)))|b)stuff +expected-stdout: + 1: no-file+(a|b)stuff + 2: no-file+(a*(c)|b)stuff + 3: no-file+((((c)))|b)stuff +--- +name: eglob-match-1 +description: + Check that the pattern matches correctly +file-setup: file 644 "abd" +file-setup: file 644 "acd" +file-setup: file 644 "abac" +stdin: + echo 1: a+(b|c)d + echo 2: a!(@(b|B))d + echo 3: *(a(b|c)) # (...|...) can be used within X(..) + echo 4: a[b*(foo|bar)]d # patterns not special inside [...] +expected-stdout: + 1: abd acd + 2: acd + 3: abac + 4: abd +--- +name: eglob-case-1 +description: + Simple negation tests +stdin: + case foo in !(foo|bar)) echo yes;; *) echo no;; esac + case bar in !(foo|bar)) echo yes;; *) echo no;; esac +expected-stdout: + no + no +--- +name: eglob-case-2 +description: + Simple kleene tests +stdin: + case foo in *(a|b[)) echo yes;; *) echo no;; esac + case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac + case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac +expected-stdout: + no + yes + yes +--- +name: eglob-trim-1 +description: + Eglobbing in trim expressions... + (AT&T ksh fails this - docs say # matches shortest string, ## matches + longest...) +stdin: + x=abcdef + echo 1: ${x#a|abc} + echo 2: ${x##a|abc} + echo 3: ${x%def|f} + echo 4: ${x%%f|def} +expected-stdout: + 1: bcdef + 2: def + 3: abcde + 4: abc +--- +name: eglob-trim-2 +description: + Check eglobbing works in trims... +stdin: + x=abcdef + echo 1: ${x#*(a|b)cd} + echo 2: "${x#*(a|b)cd}" + echo 3: ${x#"*(a|b)cd"} + echo 4: ${x#a(b|c)} +expected-stdout: + 1: ef + 2: ef + 3: abcdef + 4: cdef +--- +name: eglob-substrpl-1 +description: + Check eglobbing works in substs... and they work at all +stdin: + [[ -n $BASH_VERSION ]] && shopt -s extglob + x=1222321_ab/cde_b/c_1221 + y=xyz + echo 1: ${x/2} + echo 2: ${x//2} + echo 3: ${x/+(2)} + echo 4: ${x//+(2)} + echo 5: ${x/2/4} + echo 6: ${x//2/4} + echo 7: ${x/+(2)/4} + echo 8: ${x//+(2)/4} + echo 9: ${x/b/c/e/f} + echo 10: ${x/b\/c/e/f} + echo 11: ${x/b\/c/e\/f} + echo 12: ${x/b\/c/e\\/f} + echo 13: ${x/b\\/c/e\\/f} + echo 14: ${x//b/c/e/f} + echo 15: ${x//b\/c/e/f} + echo 16: ${x//b\/c/e\/f} + echo 17: ${x//b\/c/e\\/f} + echo 18: ${x//b\\/c/e\\/f} + echo 19: ${x/b\/*\/c/x} + echo 20: ${x/\//.} + echo 21: ${x//\//.} + echo 22: ${x///.} + echo 23: ${x//#1/9} + echo 24: ${x//%1/9} + echo 25: ${x//\%1/9} + echo 26: ${x//\\%1/9} + echo 27: ${x//\a/9} + echo 28: ${x//\\a/9} + echo 29: ${x/2/$y} +expected-stdout: + 1: 122321_ab/cde_b/c_1221 + 2: 131_ab/cde_b/c_11 + 3: 1321_ab/cde_b/c_1221 + 4: 131_ab/cde_b/c_11 + 5: 1422321_ab/cde_b/c_1221 + 6: 1444341_ab/cde_b/c_1441 + 7: 14321_ab/cde_b/c_1221 + 8: 14341_ab/cde_b/c_141 + 9: 1222321_ac/e/f/cde_b/c_1221 + 10: 1222321_ae/fde_b/c_1221 + 11: 1222321_ae/fde_b/c_1221 + 12: 1222321_ae\/fde_b/c_1221 + 13: 1222321_ab/cde_b/c_1221 + 14: 1222321_ac/e/f/cde_c/e/f/c_1221 + 15: 1222321_ae/fde_e/f_1221 + 16: 1222321_ae/fde_e/f_1221 + 17: 1222321_ae\/fde_e\/f_1221 + 18: 1222321_ab/cde_b/c_1221 + 19: 1222321_ax_1221 + 20: 1222321_ab.cde_b/c_1221 + 21: 1222321_ab.cde_b.c_1221 + 22: 1222321_ab/cde_b/c_1221 + 23: 9222321_ab/cde_b/c_1221 + 24: 1222321_ab/cde_b/c_1229 + 25: 1222321_ab/cde_b/c_1229 + 26: 1222321_ab/cde_b/c_1221 + 27: 1222321_9b/cde_b/c_1221 + 28: 1222321_9b/cde_b/c_1221 + 29: 1xyz22321_ab/cde_b/c_1221 +--- +name: eglob-substrpl-2 +description: + Check anchored substring replacement works, corner cases +stdin: + foo=123 + echo 1: ${foo/#/x} + echo 2: ${foo/%/x} + echo 3: ${foo/#/} + echo 4: ${foo/#} + echo 5: ${foo/%/} + echo 6: ${foo/%} +expected-stdout: + 1: x123 + 2: 123x + 3: 123 + 4: 123 + 5: 123 + 6: 123 +--- +name: eglob-substrpl-3a +description: + Check substring replacement works with variables and slashes, too +stdin: + pfx=/home/user + wd=/home/user/tmp + echo "${wd/#$pfx/~}" + echo "${wd/#\$pfx/~}" + echo "${wd/#"$pfx"/~}" + echo "${wd/#'$pfx'/~}" + echo "${wd/#"\$pfx"/~}" + echo "${wd/#'\$pfx'/~}" +expected-stdout: + ~/tmp + /home/user/tmp + ~/tmp + /home/user/tmp + /home/user/tmp + /home/user/tmp +--- +name: eglob-substrpl-3b +description: + More of this, bash fails it (bash4 passes) +stdin: + pfx=/home/user + wd=/home/user/tmp + echo "${wd/#$(echo /home/user)/~}" + echo "${wd/#"$(echo /home/user)"/~}" + echo "${wd/#'$(echo /home/user)'/~}" +expected-stdout: + ~/tmp + ~/tmp + /home/user/tmp +--- +name: eglob-substrpl-3c +description: + Even more weird cases +stdin: + pfx=/home/user + wd='$pfx/tmp' + echo 1: ${wd/#$pfx/~} + echo 2: ${wd/#\$pfx/~} + echo 3: ${wd/#"$pfx"/~} + echo 4: ${wd/#'$pfx'/~} + echo 5: ${wd/#"\$pfx"/~} + echo 6: ${wd/#'\$pfx'/~} + ts='a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp)' + tp=a/b + tr=c/d + [[ -n $BASH_VERSION ]] && shopt -s extglob + echo 7: ${ts/a\/b/$tr} + echo 8: ${ts/a\/b/\$tr} + echo 9: ${ts/$tp/$tr} + echo 10: ${ts/\$tp/$tr} + echo 11: ${ts/\\$tp/$tr} + echo 12: ${ts/$tp/c/d} + echo 13: ${ts/$tp/c\/d} + echo 14: ${ts/$tp/c\\/d} + echo 15: ${ts/+(a\/b)/$tr} + echo 16: ${ts/+(a\/b)/\$tr} + echo 17: ${ts/+($tp)/$tr} + echo 18: ${ts/+($tp)/c/d} + echo 19: ${ts/+($tp)/c\/d} + echo 25: ${ts//a\/b/$tr} + echo 26: ${ts//a\/b/\$tr} + echo 27: ${ts//$tp/$tr} + echo 28: ${ts//$tp/c/d} + echo 29: ${ts//$tp/c\/d} + echo 30: ${ts//+(a\/b)/$tr} + echo 31: ${ts//+(a\/b)/\$tr} + echo 32: ${ts//+($tp)/$tr} + echo 33: ${ts//+($tp)/c/d} + echo 34: ${ts//+($tp)/c\/d} + tp="+($tp)" + echo 40: ${ts/$tp/$tr} + echo 41: ${ts//$tp/$tr} +expected-stdout: + 1: $pfx/tmp + 2: ~/tmp + 3: $pfx/tmp + 4: ~/tmp + 5: ~/tmp + 6: ~/tmp + 7: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 8: $tra/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 9: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 10: a/ba/bc/d$tp_a/b$tp_*(a/b)_*($tp) + 11: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 12: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 13: c/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 14: c\/da/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 15: c/d$tp$tp_a/b$tp_*(a/b)_*($tp) + 16: $tr$tp$tp_a/b$tp_*(a/b)_*($tp) + 17: c/d$tp$tp_a/b$tp_*(a/b)_*($tp) + 18: c/d$tp$tp_a/b$tp_*(a/b)_*($tp) + 19: c/d$tp$tp_a/b$tp_*(a/b)_*($tp) + 25: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 26: $tr$tr$tp$tp_$tr$tp_*($tr)_*($tp) + 27: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 28: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 29: c/dc/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 30: c/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 31: $tr$tp$tp_$tr$tp_*($tr)_*($tp) + 32: c/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 33: c/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 34: c/d$tp$tp_c/d$tp_*(c/d)_*($tp) + 40: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp) + 41: a/ba/b$tp$tp_a/b$tp_*(a/b)_*($tp) +# This is what GNU bash does: +# 40: c/d$tp$tp_a/b$tp_*(a/b)_*($tp) +# 41: c/d$tp$tp_c/d$tp_*(c/d)_*($tp) +--- +name: eglob-utf8-1 +description: + UTF-8 mode differences for eglobbing +stdin: + s=blöd + set +U + print 1: ${s%???} . + print 2: ${s/b???d/x} . + set -U + print 3: ${s%???} . + print 4: ${s/b??d/x} . + x=nö + print 5: ${x%?} ${x%%?} . + x=äh + print 6: ${x#?} ${x##?} . + x= + print 7: ${x%?} ${x%%?} . + x=mä + print 8: ${x%?} ${x%%?} . + x=何 + print 9: ${x%?} ${x%%?} . +expected-stdout: + 1: bl . + 2: x . + 3: b . + 4: x . + 5: n n . + 6: h h . + 7: . + 8: mä mä . + 9: . +--- +name: glob-bad-1 +description: + Check that globbing isn't done when glob has syntax error +file-setup: dir 755 "[x" +file-setup: file 644 "[x/foo" +stdin: + echo [* + echo *[x + echo [x/* +expected-stdout: + [* + *[x + [x/foo +--- +name: glob-bad-2 +description: + Check that symbolic links aren't stat()'d +file-setup: dir 755 "dir" +file-setup: symlink 644 "dir/abc" + non-existent-file +stdin: + echo d*/* + echo d*/abc +expected-stdout: + dir/abc + dir/abc +--- +name: glob-range-1 +description: + Test range matching +file-setup: file 644 ".bc" +file-setup: file 644 "abc" +file-setup: file 644 "bbc" +file-setup: file 644 "cbc" +file-setup: file 644 "-bc" +stdin: + echo [ab-]* + echo [-ab]* + echo [!-ab]* + echo [!ab]* + echo []ab]* +expected-stdout: + -bc abc bbc + -bc abc bbc + cbc + -bc cbc + abc bbc +--- +name: glob-range-2 +description: + Test range matching + (AT&T ksh fails this; POSIX says invalid) +file-setup: file 644 "abc" +stdin: + echo [a--]* +expected-stdout: + [a--]* +--- +name: glob-range-3 +description: + Check that globbing matches the right things... +# breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition) +category: !os:darwin +file-setup: file 644 "ac" +stdin: + echo a[-]* +expected-stdout: + ac +--- +name: glob-range-4 +description: + Results unspecified according to POSIX +file-setup: file 644 ".bc" +stdin: + echo [a.]* +expected-stdout: + [a.]* +--- +name: glob-range-5 +description: + Results unspecified according to POSIX + (AT&T ksh treats this like [a-cc-e]*) +file-setup: file 644 "abc" +file-setup: file 644 "bbc" +file-setup: file 644 "cbc" +file-setup: file 644 "dbc" +file-setup: file 644 "ebc" +file-setup: file 644 "-bc" +stdin: + echo [a-c-e]* +expected-stdout: + -bc abc bbc cbc ebc +--- +name: heredoc-1 +description: + Check ordering/content of redundent here documents. +stdin: + cat << EOF1 << EOF2 + hi + EOF1 + there + EOF2 +expected-stdout: + there +--- +name: heredoc-2 +description: + Check quoted here-doc is protected. +stdin: + a=foo + cat << 'EOF' + hi\ + there$a + stuff + EO\ + F + EOF +expected-stdout: + hi\ + there$a + stuff + EO\ + F +--- +name: heredoc-3 +description: + Check that newline isn't needed after heredoc-delimiter marker. +stdin: ! + cat << EOF + hi + there + EOF +expected-stdout: + hi + there +--- +name: heredoc-4 +description: + Check that an error occurs if the heredoc-delimiter is missing. +stdin: ! + cat << EOF + hi + there +expected-exit: e > 0 +expected-stderr-pattern: /.*/ +--- +name: heredoc-5 +description: + Check that backslash quotes a $, ` and \ and kills a \newline +stdin: + a=BAD + b=ok + cat << EOF + h\${a}i + h\\${b}i + th\`echo not-run\`ere + th\\`echo is-run`ere + fol\\ks + more\\ + last \ + line + EOF +expected-stdout: + h${a}i + h\oki + th`echo not-run`ere + th\is-runere + fol\ks + more\ + last line +--- +name: heredoc-6 +description: + Check that \newline in initial here-delim word doesn't imply + a quoted here-doc. +stdin: + a=i + cat << EO\ + F + h$a + there + EOF +expected-stdout: + hi + there +--- +name: heredoc-7 +description: + Check that double quoted $ expressions in here delimiters are + not expanded and match the delimiter. + POSIX says only quote removal is applied to the delimiter. +stdin: + a=b + cat << "E$a" + hi + h$a + hb + E$a + echo done +expected-stdout: + hi + h$a + hb + done +--- +name: heredoc-8 +description: + Check that double quoted escaped $ expressions in here + delimiters are not expanded and match the delimiter. + POSIX says only quote removal is applied to the delimiter + (\ counts as a quote). +stdin: + a=b + cat << "E\$a" + hi + h$a + h\$a + hb + h\b + E$a + echo done +expected-stdout: + hi + h$a + h\$a + hb + h\b + done +--- +name: heredoc-9a +description: + Check that here strings work. +stdin: + bar="bar + baz" + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo + "$__progname" -c "tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<foo" + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<"$bar" + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<'$bar' + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<\$bar + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<-foo +expected-stdout: + sbb + sbb + one + onm + $one + $one + -sbb +--- +name: heredoc-9b +description: + Check that a corner case of here strings works like bash +stdin: + fnord=42 + bar="bar + \$fnord baz" + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<$bar +expected-stdout: + one $sabeq onm +category: bash +--- +name: heredoc-9c +description: + Check that a corner case of here strings works like ksh93, zsh +stdin: + fnord=42 + bar="bar + \$fnord baz" + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<<$bar +expected-stdout: + one + $sabeq onm +--- +name: heredoc-9d +description: + Check another corner case of here strings +stdin: + tr abcdefghijklmnopqrstuvwxyz nopqrstuvwxyzabcdefghijklm <<< bar +expected-stdout: + one +--- +name: heredoc-quoting-unsubst +description: + Check for correct handling of quoted characters in + here documents without substitution (marker is quoted). +stdin: + foo=bar + cat <<-'EOF' + x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x + EOF +expected-stdout: + x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x +--- +name: heredoc-quoting-subst +description: + Check for correct handling of quoted characters in + here documents with substitution (marker is not quoted). +stdin: + foo=bar + cat <<-EOF + x " \" \ \\ $ \$ `echo baz` \`echo baz\` $foo \$foo x + EOF +expected-stdout: + x " \" \ \ $ $ baz `echo baz` bar $foo x +--- +name: heredoc-tmpfile-1 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in simple command. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF + hi + EOF + for i in a b ; do + cat <<- EOF + more + EOF + done + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + more + more + Left overs: * +--- +name: heredoc-tmpfile-2 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in function, multiple calls to function. +stdin: + TMPDIR=$PWD + eval ' + foo() { + cat <<- EOF + hi + EOF + } + foo + foo + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + hi + Left overs: * +--- +name: heredoc-tmpfile-3 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in function in loop, multiple calls to function. +stdin: + TMPDIR=$PWD + eval ' + foo() { + cat <<- EOF + hi + EOF + } + for i in a b; do + foo + foo() { + cat <<- EOF + folks $i + EOF + } + done + foo + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + folks b + folks b + Left overs: * +--- +name: heredoc-tmpfile-4 +description: + Check that heredoc temp files aren't removed too soon or too late. + Backgrounded simple command with here doc +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF & + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + Left overs: * +--- +name: heredoc-tmpfile-5 +description: + Check that heredoc temp files aren't removed too soon or too late. + Backgrounded subshell command with here doc +stdin: + TMPDIR=$PWD + eval ' + ( + sleep 1 # so parent exits + echo A + cat <<- EOF + hi + EOF + echo B + ) & + ' & + sleep 2 + echo Left overs: * +expected-stdout: + A + hi + B + Left overs: * +--- +name: heredoc-tmpfile-6 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in pipeline. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF | sed "s/hi/HI/" + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + HI + Left overs: * +--- +name: heredoc-tmpfile-7 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in backgrounded pipeline. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF | sed 's/hi/HI/' & + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + HI + Left overs: * +--- +name: heredoc-tmpfile-8 +description: + Check that heredoc temp files aren't removed too soon or too + late. Heredoc in function, backgrounded call to function. + This check can fail on slow machines (<100 MHz), or Cygwin, + that's normal. +stdin: + TMPDIR=$PWD + # Background eval so main shell doesn't do parsing + eval ' + foo() { + cat <<- EOF + hi + EOF + } + foo + # sleep so eval can die + (sleep 1; foo) & + (sleep 1; foo) & + foo + ' & + sleep 2 + echo Left overs: * +expected-stdout: + hi + hi + hi + hi + Left overs: * +--- +name: history-basic +description: + See if we can test history at all +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo hi + fc -l +expected-stdout: + hi + 1 echo hi +expected-stderr-pattern: + /^X*$/ +--- +name: history-dups +description: + Verify duplicates and spaces are not entered +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo hi + echo yo + echo hi + fc -l +expected-stdout: + hi + yo + hi + 1 echo hi +expected-stderr-pattern: + /^X*$/ +--- +name: history-unlink +description: + Check if broken HISTFILEs do not cause trouble +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=foo/hist.file! +file-setup: file 644 "Env" + PS1=X +file-setup: dir 755 "foo" +file-setup: file 644 "foo/hist.file" + sometext +time-limit: 5 +perl-setup: chmod(0555, "foo"); +stdin: + echo hi + fc -l + chmod 0755 foo +expected-stdout: + hi + 1 echo hi +expected-stderr-pattern: + /(.*cannot unlink HISTFILE.*\n)?X*$/ +--- +name: history-e-minus-1 +description: + Check if more recent command is executed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo hi + echo there + fc -e - +expected-stdout: + hi + there + there +expected-stderr-pattern: + /^X*echo there\nX*$/ +--- +name: history-e-minus-2 +description: + Check that repeated command is printed before command + is re-executed. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + exec 2>&1 + echo hi + echo there + fc -e - +expected-stdout-pattern: + /X*hi\nX*there\nX*echo there\nthere\nX*/ +expected-stderr-pattern: + /^X*$/ +--- +name: history-e-minus-3 +description: + fc -e - fails when there is no history + (ksh93 has a bug that causes this to fail) + (ksh88 loops on this) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + fc -e - + echo ok +expected-stdout: + ok +expected-stderr-pattern: + /^X*.*:.*history.*\nX*$/ +--- +name: history-e-minus-4 +description: + Check if "fc -e -" command output goes to stdout. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc + fc -e - | (read x; echo "A $x") + echo ok +expected-stdout: + abc + A abc + ok +expected-stderr-pattern: + /^X*echo abc\nX*/ +--- +name: history-e-minus-5 +description: + fc is replaced in history by new command. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + : + fc -e - echo + fc -l 2 5 +expected-stdout: + abc def + ghi jkl + ghi jkl + 2 echo ghi jkl + 3 : + 4 echo ghi jkl + 5 fc -l 2 5 +expected-stderr-pattern: + /^X*echo ghi jkl\nX*$/ +--- +name: history-list-1 +description: + List lists correct range + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + fc -l -- -2 +expected-stdout: + line 1 + line 2 + line 3 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-2 +description: + Lists oldest history if given pre-historic number + (ksh93 has a bug that causes this to fail) + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + fc -l -- -40 +expected-stdout: + line 1 + line 2 + line 3 + 1 echo line 1 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-3 +description: + Can give number 'options' to fc +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -3 -2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-4 +description: + -1 refers to previous command +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -1 -1 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 4 echo line 4 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-5 +description: + List command stays in history +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -1 -1 + fc -l -2 -1 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 4 echo line 4 + 4 echo line 4 + 5 fc -l -1 -1 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-6 +description: + HISTSIZE limits about of history kept. + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 5 echo line 5 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-7 +description: + fc allows too old/new errors in range specification +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l 1 30 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 5 echo line 5 + 6 fc -l 1 30 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-r-1 +description: + test -r flag in history +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l -r 2 4 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 3 echo line 3 + 2 echo line 2 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-r-2 +description: + If first is newer than last, -r is implied. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l 4 2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 3 echo line 3 + 2 echo line 2 +expected-stderr-pattern: + /^X*$/ +--- +name: history-list-r-3 +description: + If first is newer than last, -r is cancelled. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l -r 4 2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 2 echo line 2 + 3 echo line 3 + 4 echo line 4 +expected-stderr-pattern: + /^X*$/ +--- +name: history-subst-1 +description: + Basic substitution +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - abc=AB 'echo a' +expected-stdout: + abc def + ghi jkl + AB def +expected-stderr-pattern: + /^X*echo AB def\nX*$/ +--- +name: history-subst-2 +description: + Does subst find previous command? +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - jkl=XYZQRT 'echo g' +expected-stdout: + abc def + ghi jkl + ghi XYZQRT +expected-stderr-pattern: + /^X*echo ghi XYZQRT\nX*$/ +--- +name: history-subst-3 +description: + Does subst find previous command when no arguments given +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - jkl=XYZQRT +expected-stdout: + abc def + ghi jkl + ghi XYZQRT +expected-stderr-pattern: + /^X*echo ghi XYZQRT\nX*$/ +--- +name: history-subst-4 +description: + Global substitutions work + (ksh88 and ksh93 do not have -g option) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def asjj sadjhasdjh asdjhasd + fc -e - -g a=FooBAR +expected-stdout: + abc def asjj sadjhasdjh asdjhasd + FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd +expected-stderr-pattern: + /^X*echo FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd\nX*$/ +--- +name: history-subst-5 +description: + Make sure searches don't find current (fc) command + (ksh88/ksh93 don't have the ? prefix thing so they fail this test) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - abc=AB \?abc +expected-stdout: + abc def + ghi jkl + AB def +expected-stderr-pattern: + /^X*echo AB def\nX*$/ +--- +name: history-ed-1-old +description: + Basic (ed) editing works (assumes you have generic ed editor + that prints no prompts). This is for oldish ed(1) which write + the character count to stdout. +category: stdout-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + w + q +expected-stdout: + abc def + 13 + 16 + FOOBAR def +expected-stderr-pattern: + /^X*echo FOOBAR def\nX*$/ +--- +name: history-ed-2-old +description: + Correct command is edited when number given +category: stdout-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 is here + echo line 3 + echo line 4 + fc 2 + s/is here/is changed/ + w + q +expected-stdout: + line 1 + line 2 is here + line 3 + line 4 + 20 + 23 + line 2 is changed +expected-stderr-pattern: + /^X*echo line 2 is changed\nX*$/ +--- +name: history-ed-3-old +description: + Newly created multi line commands show up as single command + in history. + (NOTE: adjusted for COMPLEX HISTORY compile time option) + (ksh88 fails 'cause it lists the fc command) +category: stdout-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + $a + echo a new line + . + w + q + fc -l +expected-stdout: + abc def + 13 + 32 + FOOBAR def + a new line + 1 echo abc def + 2 echo FOOBAR def + 3 echo a new line +expected-stderr-pattern: + /^X*echo FOOBAR def\necho a new line\nX*$/ +--- +name: history-ed-1 +description: + Basic (ed) editing works (assumes you have generic ed editor + that prints no prompts). This is for newish ed(1) and stderr. +category: !no-stderr-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + w + q +expected-stdout: + abc def + FOOBAR def +expected-stderr-pattern: + /^X*13\n16\necho FOOBAR def\nX*$/ +--- +name: history-ed-2 +description: + Correct command is edited when number given +category: !no-stderr-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 is here + echo line 3 + echo line 4 + fc 2 + s/is here/is changed/ + w + q +expected-stdout: + line 1 + line 2 is here + line 3 + line 4 + line 2 is changed +expected-stderr-pattern: + /^X*20\n23\necho line 2 is changed\nX*$/ +--- +name: history-ed-3 +description: + Newly created multi line commands show up as single command + in history. +category: !no-stderr-ed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + $a + echo a new line + . + w + q + fc -l +expected-stdout: + abc def + FOOBAR def + a new line + 1 echo abc def + 2 echo FOOBAR def + 3 echo a new line +expected-stderr-pattern: + /^X*13\n32\necho FOOBAR def\necho a new line\nX*$/ +--- +name: IFS-space-1 +description: + Simple test, default IFS +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> <A> <B> <C> + <2> <A B C> + <3> <A> <B> <C> + <4> <A> <B> <C> +--- +name: IFS-colon-1 +description: + Simple test, IFS=: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS=: + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> <A> <B> <C> + <2> <A:B:C> + <3> <A> <B> <C> + <4> <A> <B> <C> +--- +name: IFS-null-1 +description: + Simple test, IFS="" +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="" + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> <A B C> + <2> <ABC> + <3> <A B C> + <4> <A B C> +--- +name: IFS-space-colon-1 +description: + Simple test, IFS=<white-space>: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + set -- + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" + showargs 5 : "$@" +expected-stdout: + <1> + <2> <> + <3> + <4> + <5> <:> +--- +name: IFS-space-colon-2 +description: + Simple test, IFS=<white-space>: + AT&T ksh fails this, POSIX says the test is correct. +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + set -- + showargs :"$@" +expected-stdout: + <:> +--- +name: IFS-space-colon-3 +description: + Simple test, IFS=<white-space>: + pdksh fails both of these tests + not sure whether #2 is correct +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + x= + set -- + showargs "$x$@" 1 + showargs "$@$x" 2 +expected-fail: yes +expected-stdout: + <> <1> + <> <2> +--- +name: IFS-space-colon-4 +description: + Simple test, IFS=<white-space>: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + set -- + showargs "$@$@" +expected-stdout: + +--- +name: IFS-space-colon-5 +description: + Simple test, IFS=<white-space>: + Don't know what POSIX thinks of this. AT&T ksh does not do this. +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + set -- + showargs "${@:-}" +expected-stdout: + <> +--- +name: IFS-subst-1 +description: + Simple test, IFS=<white-space>: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + x=":b: :" + echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo + echo -n '2:'; for i in :b:: ; do echo -n " [$i]" ; done ; echo + showargs 3 $x + showargs 4 :b:: + x="a:b:" + echo -n '5:'; for i in $x ; do echo -n " [$i]" ; done ; echo + showargs 6 $x + x="a::c" + echo -n '7:'; for i in $x ; do echo -n " [$i]" ; done ; echo + showargs 8 $x + echo -n '9:'; for i in ${FOO-`echo -n h:i`th:ere} ; do echo -n " [$i]" ; done ; echo + showargs 10 ${FOO-`echo -n h:i`th:ere} + showargs 11 "${FOO-`echo -n h:i`th:ere}" + x=" A : B::D" + echo -n '12:'; for i in $x ; do echo -n " [$i]" ; done ; echo + showargs 13 $x +expected-stdout: + 1: [] [b] [] + 2: [:b::] + <3> <> <b> <> + <4> <:b::> + 5: [a] [b] + <6> <a> <b> + 7: [a] [] [c] + <8> <a> <> <c> + 9: [h] [ith] [ere] + <10> <h> <ith> <ere> + <11> <h:ith:ere> + 12: [A] [B] [] [D] + <13> <A> <B> <> <D> +--- +name: integer-base-err-1 +description: + Can't have 0 base (causes shell to exit) +expected-exit: e != 0 +stdin: + typeset -i i + i=3 + i=0#4 + echo $i +expected-stderr-pattern: + /^.*:.*0#4.*\n$/ +--- +name: integer-base-err-2 +description: + Can't have multiple bases in a 'constant' (causes shell to exit) + (ksh88 fails this test) +expected-exit: e != 0 +stdin: + typeset -i i + i=3 + i=2#110#11 + echo $i +expected-stderr-pattern: + /^.*:.*2#110#11.*\n$/ +--- +name: integer-base-err-3 +description: + Syntax errors in expressions and effects on bases + (interactive so errors don't cause exits) + (ksh88 fails this test - shell exits, even with -i) +arguments: !-i! +stdin: + PS1= # minimise prompt hassles + typeset -i4 a=10 + typeset -i a=2+ + echo $a + typeset -i4 a=10 + typeset -i2 a=2+ + echo $a +expected-stderr-pattern: + /^([#\$] )?.*:.*2+.*\n.*:.*2+.*\n$/ +expected-stdout: + 4#22 + 4#22 +--- +name: integer-base-err-4 +description: + Are invalid digits (according to base) errors? + (ksh93 fails this test) +expected-exit: e != 0 +stdin: + typeset -i i; + i=3#4 +expected-stderr-pattern: + /^([#\$] )?.*:.*3#4.*\n$/ +--- +name: integer-base-1 +description: + Missing number after base is treated as 0. +stdin: + typeset -i i + i=3 + i=2# + echo $i +expected-stdout: + 0 +--- +name: integer-base-2 +description: + Check 'stickyness' of base in various situations +stdin: + typeset -i i=8 + echo $i + echo ---------- A + typeset -i4 j=8 + echo $j + echo ---------- B + typeset -i k=8 + typeset -i4 k=8 + echo $k + echo ---------- C + typeset -i4 l + l=3#10 + echo $l + echo ---------- D + typeset -i m + m=3#10 + echo $m + echo ---------- E + n=2#11 + typeset -i n + echo $n + n=10 + echo $n + echo ---------- F + typeset -i8 o=12 + typeset -i4 o + echo $o + echo ---------- G + typeset -i p + let p=8#12 + echo $p +expected-stdout: + 8 + ---------- A + 4#20 + ---------- B + 4#20 + ---------- C + 4#3 + ---------- D + 3#10 + ---------- E + 2#11 + 2#1010 + ---------- F + 4#30 + ---------- G + 8#12 +--- +name: integer-base-3 +description: + More base parsing (hmm doesn't test much..) +stdin: + typeset -i aa + aa=1+12#10+2 + echo $aa + typeset -i bb + bb=1+$aa + echo $bb + typeset -i bb + bb=$aa + echo $bb + typeset -i cc + cc=$aa + echo $cc +expected-stdout: + 15 + 16 + 15 + 15 +--- +name: integer-base-4 +description: + Check that things not declared as integers are not made integers, + also, check if base is not reset by -i with no arguments. + (ksh93 fails - prints 10#20 - go figure) +stdin: + xx=20 + let xx=10 + typeset -i | grep '^xx=' + typeset -i4 a=10 + typeset -i a=20 + echo $a +expected-stdout: + 4#110 +--- +name: integer-base-5 +description: + More base stuff +stdin: + typeset -i4 a=3#10 + echo $a + echo -- + typeset -i j=3 + j='~3' + echo $j + echo -- + typeset -i k=1 + x[k=k+1]=3 + echo $k + echo -- + typeset -i l + for l in 1 2+3 4; do echo $l; done +expected-stdout: + 4#3 + -- + -4 + -- + 2 + -- + 1 + 5 + 4 +--- +name: integer-base-6 +description: + Even more base stuff + (ksh93 fails this test - prints 0) +stdin: + typeset -i7 i + i= + echo $i +expected-stdout: + 7#0 +--- +name: integer-base-7 +description: + Check that non-integer parameters don't get bases assigned +stdin: + echo $(( zz = 8#100 )) + echo $zz +expected-stdout: + 64 + 64 +--- +name: lineno-stdin +description: + See if $LINENO is updated and can be modified. +stdin: + echo A $LINENO + echo B $LINENO + LINENO=20 + echo C $LINENO +expected-stdout: + A 1 + B 2 + C 20 +--- +name: lineno-inc +description: + See if $LINENO is set for .'d files. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO + LINENO=20 + echo dot C $LINENO +stdin: + echo A $LINENO + echo B $LINENO + . ./dotfile +expected-stdout: + A 1 + B 2 + dot A 1 + dot B 2 + dot C 20 +--- +name: lineno-func +description: + See if $LINENO is set for commands in a function. +stdin: + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + echo C $LINENO +expected-stdout: + A 1 + B 2 + func A 4 + func B 5 + C 8 +--- +name: lineno-unset +description: + See if unsetting LINENO makes it non-magic. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO +stdin: + unset LINENO + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + . ./dotfile + echo C $LINENO +expected-stdout: + A + B + func A + func B + dot A + dot B + C +--- +name: lineno-unset-use +description: + See if unsetting LINENO makes it non-magic even + when it is re-used. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO +stdin: + unset LINENO + LINENO=3 + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + . ./dotfile + echo C $LINENO +expected-stdout: + A 3 + B 3 + func A 3 + func B 3 + dot A 3 + dot B 3 + C 3 +--- +name: lineno-trap +description: + Check if LINENO is tracked in traps +stdin: + fail() { + echo "line <$1>" + exit 1 + } + trap 'fail $LINENO' INT ERR + false +expected-stdout: + line <6> +expected-exit: 1 +--- +name: read-IFS-1 +description: + Simple test, default IFS +stdin: + echo "A B " > IN + unset x y z + read x y z < IN + echo 1: "x[$x] y[$y] z[$z]" + echo 1a: ${z-z not set} + read x < IN + echo 2: "x[$x]" +expected-stdout: + 1: x[A] y[B] z[] + 1a: + 2: x[A B] +--- +name: read-ksh-1 +description: + If no var specified, REPLY is used +stdin: + echo "abc" > IN + read < IN + echo "[$REPLY]"; +expected-stdout: + [abc] +--- +name: regression-1 +description: + Lex array code had problems with this. +stdin: + echo foo[ + n=bar + echo "hi[ $n ]=1" +expected-stdout: + foo[ + hi[ bar ]=1 +--- +name: regression-2 +description: + When PATH is set before running a command, the new path is + not used in doing the path search + $ echo echo hi > /tmp/q ; chmod a+rx /tmp/q + $ PATH=/tmp q + q: not found + $ + in comexec() the two lines + while (*vp != NULL) + (void) typeset(*vp++, xxx, 0); + need to be moved out of the switch to before findcom() is + called - I don't know what this will break. +stdin: + : ${PWD:-`pwd 2> /dev/null`} + : ${PWD:?"PWD not set - can't do test"} + mkdir Y + cat > Y/xxxscript << EOF + #!/bin/sh + # Need to restore path so echo can be found (some shells don't have + # it as a built-in) + PATH=\$OLDPATH + echo hi + exit 0 + EOF + chmod a+rx Y/xxxscript + export OLDPATH="$PATH" + PATH=$PWD/Y xxxscript + exit $? +expected-stdout: + hi +--- +name: regression-6 +description: + Parsing of $(..) expressions is non-optimal. It is + impossible to have any parentheses inside the expression. + I.e., + $ ksh -c 'echo $(echo \( )' + no closing quote + $ ksh -c 'echo $(echo "(" )' + no closing quote + $ + The solution is to hack the parsing clode in lex.c, the + question is how to hack it: should any parentheses be + escaped by a backslash, or should recursive parsing be done + (so quotes could also be used to hide hem). The former is + easier, the later better... +stdin: + echo $(echo \() +expected-stdout: + ( +--- +name: regression-9 +description: + Continue in a for loop does not work right: + for i in a b c ; do + if [ $i = b ] ; then + continue + fi + echo $i + done + Prints a forever... +stdin: + first=yes + for i in a b c ; do + if [ $i = b ] ; then + if [ $first = no ] ; then + echo 'continue in for loop broken' + break # hope break isn't broken too :-) + fi + first=no + continue + fi + done + echo bye +expected-stdout: + bye +--- +name: regression-10 +description: + The following: + set -- `false` + echo $? + should print 0 according to POSIX (dash, bash, ksh93, posh) + but not 0 according to the getopt(1) manual page, ksh88, and + Bourne sh (such as /bin/sh on Solaris). + In mksh R39b, we honour POSIX except when -o sh is set. +stdin: + showf() { + [[ -o posix ]]; FPOSIX=$((1-$?)) + [[ -o sh ]]; FSH=$((1-$?)) + echo -n "FPOSIX=$FPOSIX FSH=$FSH " + } + set +o posix +o sh + showf + set -- `false` + echo rv=$? + set -o sh + showf + set -- `false` + echo rv=$? + set -o posix + showf + set -- `false` + echo rv=$? +expected-stdout: + FPOSIX=0 FSH=0 rv=0 + FPOSIX=0 FSH=1 rv=1 + FPOSIX=1 FSH=0 rv=0 +--- +name: regression-11 +description: + The following: + x=/foo/bar/blah + echo ${x##*/} + should echo blah but on some machines echos /foo/bar/blah. +stdin: + x=/foo/bar/blah + echo ${x##*/} +expected-stdout: + blah +--- +name: regression-12 +description: + Both of the following echos produce the same output under sh/ksh.att: + #!/bin/sh + x="foo bar" + echo "`echo \"$x\"`" + echo "`echo "$x"`" + pdksh produces different output for the former (foo instead of foo\tbar) +stdin: + x="foo bar" + echo "`echo \"$x\"`" + echo "`echo "$x"`" +expected-stdout: + foo bar + foo bar +--- +name: regression-13 +description: + The following command hangs forever: + $ (: ; cat /etc/termcap) | sleep 2 + This is because the shell forks a shell to run the (..) command + and this shell has the pipe open. When the sleep dies, the cat + doesn't get a SIGPIPE 'cause a process (ie, the second shell) + still has the pipe open. + + NOTE: this test provokes a bizarre bug in ksh93 (shell starts reading + commands from /etc/termcap..) +time-limit: 10 +stdin: + echo A line of text that will be duplicated quite a number of times.> t1 + cat t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 > t2 + cat t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 > t1 + cat t1 t1 t1 t1 > t2 + (: ; cat t2 2>&-) | sleep 1 +--- +name: regression-14 +description: + The command + $ (foobar) 2> /dev/null + generates no output under /bin/sh, but pdksh produces the error + foobar: not found + Also, the command + $ foobar 2> /dev/null + generates an error under /bin/sh and pdksh, but AT&T ksh88 produces + no error (redirected to /dev/null). +stdin: + (you/should/not/see/this/error/1) 2> /dev/null + you/should/not/see/this/error/2 2> /dev/null + true +--- +name: regression-15 +description: + The command + $ whence foobar + generates a blank line under pdksh and sets the exit status to 0. + AT&T ksh88 generates no output and sets the exit status to 1. Also, + the command + $ whence foobar cat + generates no output under AT&T ksh88 (pdksh generates a blank line + and /bin/cat). +stdin: + whence does/not/exist > /dev/null + echo 1: $? + echo 2: $(whence does/not/exist | wc -l) + echo 3: $(whence does/not/exist cat | wc -l) +expected-stdout: + 1: 1 + 2: 0 + 3: 0 +--- +name: regression-16 +description: + ${var%%expr} seems to be broken in many places. On the mips + the commands + $ read line < /etc/passwd + $ echo $line + root:0:1:... + $ echo ${line%%:*} + root + $ echo $line + root + $ + change the value of line. On sun4s & pas, the echo ${line%%:*} doesn't + work. Haven't checked elsewhere... +script: + read x + y=$x + echo ${x%%:*} + echo $x +stdin: + root:asdjhasdasjhs:0:1:Root:/:/bin/sh +expected-stdout: + root + root:asdjhasdasjhs:0:1:Root:/:/bin/sh +--- +name: regression-17 +description: + The command + . /foo/bar + should set the exit status to non-zero (sh and AT&T ksh88 do). + XXX doting a non existent file is a fatal error for a script +stdin: + . does/not/exist +expected-exit: e != 0 +expected-stderr-pattern: /.?/ +--- +name: regression-19 +description: + Both of the following echos should produce the same thing, but don't: + $ x=foo/bar + $ echo ${x%/*} + foo + $ echo "${x%/*}" + foo/bar +stdin: + x=foo/bar + echo "${x%/*}" +expected-stdout: + foo +--- +name: regression-21 +description: + backslash does not work as expected in case labels: + $ x='-x' + $ case $x in + -\?) echo hi + esac + hi + $ x='-?' + $ case $x in + -\\?) echo hi + esac + hi + $ +stdin: + case -x in + -\?) echo fail + esac +--- +name: regression-22 +description: + Quoting backquotes inside backquotes doesn't work: + $ echo `echo hi \`echo there\` folks` + asks for more info. sh and AT&T ksh88 both echo + hi there folks +stdin: + echo `echo hi \`echo there\` folks` +expected-stdout: + hi there folks +--- +name: regression-23 +description: + )) is not treated `correctly': + $ (echo hi ; (echo there ; echo folks)) + missing (( + $ + instead of (as sh and ksh.att) + $ (echo hi ; (echo there ; echo folks)) + hi + there + folks + $ +stdin: + ( : ; ( : ; echo hi)) +expected-stdout: + hi +--- +name: regression-25 +description: + Check reading stdin in a while loop. The read should only read + a single line, not a whole stdio buffer; the cat should get + the rest. +stdin: + (echo a; echo b) | while read x ; do + echo $x + cat > /dev/null + done +expected-stdout: + a +--- +name: regression-26 +description: + Check reading stdin in a while loop. The read should read both + lines, not just the first. +script: + a= + while [ "$a" != xxx ] ; do + last=$x + read x + cat /dev/null | sed 's/x/y/' + a=x$a + done + echo $last +stdin: + a + b +expected-stdout: + b +--- +name: regression-27 +description: + The command + . /does/not/exist + should cause a script to exit. +stdin: + . does/not/exist + echo hi +expected-exit: e != 0 +expected-stderr-pattern: /does\/not\/exist/ +--- +name: regression-28 +description: + variable assignements not detected well +stdin: + a.x=1 echo hi +expected-exit: e != 0 +expected-stderr-pattern: /a\.x=1/ +--- +name: regression-29 +description: + alias expansion different from AT&T ksh88 +stdin: + alias a='for ' b='i in' + a b hi ; do echo $i ; done +expected-stdout: + hi +--- +name: regression-30 +description: + strange characters allowed inside ${...} +stdin: + echo ${a{b}} +expected-exit: e != 0 +expected-stderr-pattern: /.?/ +--- +name: regression-31 +description: + Does read handle partial lines correctly +script: + a= ret= + while [ "$a" != xxx ] ; do + read x y z + ret=$? + a=x$a + done + echo "[$x]" + echo $ret +stdin: ! + a A aA + b B Bb + c +expected-stdout: + [c] + 1 +--- +name: regression-32 +description: + Does read set variables to null at eof? +script: + a= + while [ "$a" != xxx ] ; do + read x y z + a=x$a + done + echo 1: ${x-x not set} ${y-y not set} ${z-z not set} + echo 2: ${x:+x not null} ${y:+y not null} ${z:+z not null} +stdin: + a A Aa + b B Bb +expected-stdout: + 1: + 2: +--- +name: regression-33 +description: + Does umask print a leading 0 when umask is 3 digits? +stdin: + umask 222 + umask +expected-stdout: + 0222 +--- +name: regression-35 +description: + Tempory files used for here-docs in functions get trashed after + the function is parsed (before it is executed) +stdin: + f1() { + cat <<- EOF + F1 + EOF + f2() { + cat <<- EOF + F2 + EOF + } + } + f1 + f2 + unset -f f1 + f2 +expected-stdout: + F1 + F2 + F2 +--- +name: regression-36 +description: + Command substitution breaks reading in while loop + (test from <sjg@void.zen.oz.au>) +stdin: + (echo abcdef; echo; echo 123) | + while read line + do + # the following line breaks it + c=`echo $line | wc -c` + echo $c + done +expected-stdout: + 7 + 1 + 4 +--- +name: regression-37 +description: + Machines with broken times() (reported by <sjg@void.zen.oz.au>) + time does not report correct real time +stdin: + time sleep 1 +expected-stderr-pattern: !/^\s*0\.0[\s\d]+real|^\s*real[\s]+0+\.0/ +--- +name: regression-38 +description: + set -e doesn't ignore exit codes for if/while/until/&&/||/!. +arguments: !-e! +stdin: + if false; then echo hi ; fi + false || true + false && true + while false; do echo hi; done + echo ok +expected-stdout: + ok +--- +name: regression-39 +description: + set -e: errors in command substitutions aren't ignored + Not clear if they should be or not... bash passes here + this may actually be required for make, so changed the + test to make this an mksh feature, not a bug +arguments: !-e! +stdin: + echo `false; echo hi` +#expected-fail: yes +#expected-stdout: +# hi +expected-stdout: + +--- +name: regression-40 +description: + This used to cause a core dump +env-setup: !RANDOM=12! +stdin: + echo hi +expected-stdout: + hi +--- +name: regression-41 +description: + foo should be set to bar (should not be empty) +stdin: + foo=` + echo bar` + echo "($foo)" +expected-stdout: + (bar) +--- +name: regression-42 +description: + Can't use command line assignments to assign readonly parameters. +stdin: + foo=bar + readonly foo + foo=stuff env | grep '^foo' +expected-exit: e != 0 +expected-stderr-pattern: + /.*read *only.*/ +--- +name: regression-43 +description: + Can subshells be prefixed by redirections (historical shells allow + this) +stdin: + < /dev/null (sed 's/^/X/') +--- +name: regression-45 +description: + Parameter assignments with [] recognised correctly +stdin: + FOO=*[12] + BAR=abc[ + MORE=[abc] + JUNK=a[bc + echo "<$FOO>" + echo "<$BAR>" + echo "<$MORE>" + echo "<$JUNK>" +expected-stdout: + <*[12]> + <abc[> + <[abc]> + <a[bc> +--- +name: regression-46 +description: + Check that alias expansion works in command substitutions and + at the end of file. +stdin: + alias x='echo hi' + FOO="`x` " + echo "[$FOO]" + x +expected-stdout: + [hi ] + hi +--- +name: regression-47 +description: + Check that aliases are fully read. +stdin: + alias x='echo hi; + echo there' + x + echo done +expected-stdout: + hi + there + done +--- +name: regression-48 +description: + Check that (here doc) temp files are not left behind after an exec. +stdin: + mkdir foo || exit 1 + TMPDIR=$PWD/foo "$__progname" <<- 'EOF' + x() { + sed 's/^/X /' << E_O_F + hi + there + folks + E_O_F + echo "done ($?)" + } + echo=echo; [ -x /bin/echo ] && echo=/bin/echo + exec $echo subtest-1 hi + EOF + echo subtest-1 foo/* + TMPDIR=$PWD/foo "$__progname" <<- 'EOF' + echo=echo; [ -x /bin/echo ] && echo=/bin/echo + sed 's/^/X /' << E_O_F; exec $echo subtest-2 hi + a + few + lines + E_O_F + EOF + echo subtest-2 foo/* +expected-stdout: + subtest-1 hi + subtest-1 foo/* + X a + X few + X lines + subtest-2 hi + subtest-2 foo/* +--- +name: regression-49 +description: + Check that unset params with attributes are reported by set, those + sans attributes are not. +stdin: + unset FOO BAR + echo X$FOO + export BAR + typeset -i BLAH + set | grep FOO + set | grep BAR + set | grep BLAH +expected-stdout: + X + BAR + BLAH +--- +name: regression-50 +description: + Check that aliases do not use continuation prompt after trailing + semi-colon. +file-setup: file 644 "env" + PS1=Y + PS2=X +env-setup: !ENV=./env! +arguments: !-i! +stdin: + alias foo='echo hi ; ' + foo + foo echo there +expected-stdout: + hi + hi + there +expected-stderr: ! + YYYY +--- +name: regression-51 +description: + Check that set allows both +o and -o options on same command line. +stdin: + set a b c + set -o noglob +o allexport + echo A: $*, * +expected-stdout: + A: a b c, * +--- +name: regression-52 +description: + Check that globbing works in pipelined commands +file-setup: file 644 "env" + PS1=P +file-setup: file 644 "abc" + stuff +env-setup: !ENV=./env! +arguments: !-i! +stdin: + sed 's/^/X /' < ab* + echo mark 1 + sed 's/^/X /' < ab* | sed 's/^/Y /' + echo mark 2 +expected-stdout: + X stuff + mark 1 + Y X stuff + mark 2 +expected-stderr: ! + PPPPP +--- +name: regression-53 +description: + Check that getopts works in functions +stdin: + bfunc() { + echo bfunc: enter "(args: $*; OPTIND=$OPTIND)" + while getopts B oc; do + case $oc in + (B) + echo bfunc: B option + ;; + (*) + echo bfunc: odd option "($oc)" + ;; + esac + done + echo bfunc: leave + } + + function kfunc { + echo kfunc: enter "(args: $*; OPTIND=$OPTIND)" + while getopts K oc; do + case $oc in + (K) + echo kfunc: K option + ;; + (*) + echo bfunc: odd option "($oc)" + ;; + esac + done + echo kfunc: leave + } + + set -- -f -b -k -l + echo "line 1: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 2: ret=$?, optc=$optc, OPTIND=$OPTIND" + bfunc -BBB blah + echo "line 3: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 4: ret=$?, optc=$optc, OPTIND=$OPTIND" + kfunc -KKK blah + echo "line 5: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 6: ret=$?, optc=$optc, OPTIND=$OPTIND" + echo + + OPTIND=1 + set -- -fbkl + echo "line 10: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 20: ret=$?, optc=$optc, OPTIND=$OPTIND" + bfunc -BBB blah + echo "line 30: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 40: ret=$?, optc=$optc, OPTIND=$OPTIND" + kfunc -KKK blah + echo "line 50: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 60: ret=$?, optc=$optc, OPTIND=$OPTIND" +expected-stdout: + line 1: OPTIND=1 + line 2: ret=0, optc=f, OPTIND=2 + bfunc: enter (args: -BBB blah; OPTIND=2) + bfunc: B option + bfunc: B option + bfunc: leave + line 3: OPTIND=2 + line 4: ret=0, optc=b, OPTIND=3 + kfunc: enter (args: -KKK blah; OPTIND=1) + kfunc: K option + kfunc: K option + kfunc: K option + kfunc: leave + line 5: OPTIND=3 + line 6: ret=0, optc=k, OPTIND=4 + + line 10: OPTIND=1 + line 20: ret=0, optc=f, OPTIND=2 + bfunc: enter (args: -BBB blah; OPTIND=2) + bfunc: B option + bfunc: B option + bfunc: leave + line 30: OPTIND=2 + line 40: ret=1, optc=?, OPTIND=2 + kfunc: enter (args: -KKK blah; OPTIND=1) + kfunc: K option + kfunc: K option + kfunc: K option + kfunc: leave + line 50: OPTIND=2 + line 60: ret=1, optc=?, OPTIND=2 +--- +name: regression-54 +description: + Check that ; is not required before the then in if (( ... )) then ... +stdin: + if (( 1 )) then + echo ok dparen + fi + if [[ -n 1 ]] then + echo ok dbrackets + fi +expected-stdout: + ok dparen + ok dbrackets +--- +name: regression-55 +description: + Check ${foo:%bar} is allowed (ksh88 allows it...) +stdin: + x=fooXbarXblah + echo 1 ${x%X*} + echo 2 ${x:%X*} + echo 3 ${x%%X*} + echo 4 ${x:%%X*} + echo 5 ${x#*X} + echo 6 ${x:#*X} + echo 7 ${x##*X} + echo 8 ${x:##*X} +expected-stdout: + 1 fooXbar + 2 fooXbar + 3 foo + 4 foo + 5 barXblah + 6 barXblah + 7 blah + 8 blah +--- +name: regression-57 +description: + Check if typeset output is correct for + uninitialised array elements. +stdin: + typeset -i xxx[4] + echo A + typeset -i | grep xxx | sed 's/^/ /' + echo B + typeset | grep xxx | sed 's/^/ /' + + xxx[1]=2+5 + echo M + typeset -i | grep xxx | sed 's/^/ /' + echo N + typeset | grep xxx | sed 's/^/ /' +expected-stdout: + A + xxx + B + typeset -i xxx + M + xxx[1]=7 + N + typeset -i xxx +--- +name: regression-58 +description: + Check if trap exit is ok (exit not mistaken for signal name) +stdin: + trap 'echo hi' exit + trap exit 1 +expected-stdout: + hi +--- +name: regression-59 +description: + Check if ${#array[*]} is calculated correctly. +stdin: + a[12]=hi + a[8]=there + echo ${#a[*]} +expected-stdout: + 2 +--- +name: regression-60 +description: + Check if default exit status is previous command +stdin: + (true; exit) + echo A $? + (false; exit) + echo B $? + ( (exit 103) ; exit) + echo C $? +expected-stdout: + A 0 + B 1 + C 103 +--- +name: regression-61 +description: + Check if EXIT trap is executed for sub shells. +stdin: + trap 'echo parent exit' EXIT + echo start + (echo A; echo A last) + echo B + (echo C; trap 'echo sub exit' EXIT; echo C last) + echo parent last +expected-stdout: + start + A + A last + B + C + C last + sub exit + parent last + parent exit +--- +name: regression-62 +description: + Check if test -nt/-ot succeeds if second(first) file is missing. +stdin: + touch a + test a -nt b && echo nt OK || echo nt BAD + test b -ot a && echo ot OK || echo ot BAD +expected-stdout: + nt OK + ot OK +--- +name: regression-63 +description: + Check if typeset, export, and readonly work +stdin: + { + echo FNORD-0 + FNORD_A=1 + FNORD_B=2 + FNORD_C=3 + FNORD_D=4 + FNORD_E=5 + FNORD_F=6 + FNORD_G=7 + FNORD_H=8 + integer FNORD_E FNORD_F FNORD_G FNORD_H + export FNORD_C FNORD_D FNORD_G FNORD_H + readonly FNORD_B FNORD_D FNORD_F FNORD_H + echo FNORD-1 + export + echo FNORD-2 + export -p + echo FNORD-3 + readonly + echo FNORD-4 + readonly -p + echo FNORD-5 + typeset + echo FNORD-6 + typeset -p + echo FNORD-7 + typeset - + echo FNORD-8 + } | fgrep FNORD +expected-stdout: + FNORD-0 + FNORD-1 + FNORD_C + FNORD_D + FNORD_G + FNORD_H + FNORD-2 + export FNORD_C=3 + export FNORD_D=4 + export FNORD_G=7 + export FNORD_H=8 + FNORD-3 + FNORD_B + FNORD_D + FNORD_F + FNORD_H + FNORD-4 + readonly FNORD_B=2 + readonly FNORD_D=4 + readonly FNORD_F=6 + readonly FNORD_H=8 + FNORD-5 + typeset FNORD_A + typeset -r FNORD_B + typeset -x FNORD_C + typeset -x -r FNORD_D + typeset -i FNORD_E + typeset -i -r FNORD_F + typeset -i -x FNORD_G + typeset -i -x -r FNORD_H + FNORD-6 + typeset FNORD_A=1 + typeset -r FNORD_B=2 + typeset -x FNORD_C=3 + typeset -x -r FNORD_D=4 + typeset -i FNORD_E=5 + typeset -i -r FNORD_F=6 + typeset -i -x FNORD_G=7 + typeset -i -x -r FNORD_H=8 + FNORD-7 + FNORD_A=1 + FNORD_B=2 + FNORD_C=3 + FNORD_D=4 + FNORD_E=5 + FNORD_F=6 + FNORD_G=7 + FNORD_H=8 + FNORD-8 +--- +name: regression-64 +description: + Check that we can redefine functions calling time builtin +stdin: + t() { + time >/dev/null + } + t 2>/dev/null + t() { + time + } +--- +name: syntax-1 +description: + Check that lone ampersand is a syntax error +stdin: + & +expected-exit: e != 0 +expected-stderr-pattern: + /syntax error/ +--- +name: xxx-quoted-newline-1 +description: + Check that \<newline> works inside of ${} +stdin: + abc=2 + echo ${ab\ + c} +expected-stdout: + 2 +--- +name: xxx-quoted-newline-2 +description: + Check that \<newline> works at the start of a here document +stdin: + cat << EO\ + F + hi + EOF +expected-stdout: + hi +--- +name: xxx-quoted-newline-3 +description: + Check that \<newline> works at the end of a here document +stdin: + cat << EOF + hi + EO\ + F +expected-stdout: + hi +--- +name: xxx-multi-assignment-cmd +description: + Check that assignments in a command affect subsequent assignments + in the same command +stdin: + FOO=abc + FOO=123 BAR=$FOO + echo $BAR +expected-stdout: + 123 +--- +name: xxx-multi-assignment-posix-cmd +description: + Check that the behaviour for multiple assignments with a + command name matches POSIX. See: + http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925 +stdin: + X=a Y=b; X=$Y Y=$X "$__progname" -c 'echo 1 $X $Y .'; echo 2 $X $Y . + unset X Y Z + X=a Y=${X=b} Z=$X "$__progname" -c 'echo 3 $Z .' + unset X Y Z + X=a Y=${X=b} Z=$X; echo 4 $Z . +expected-stdout: + 1 b a . + 2 a b . + 3 b . + 4 a . +--- +name: xxx-multi-assignment-posix-nocmd +description: + Check that the behaviour for multiple assignments with no + command name matches POSIX (Debian #334182). See: + http://thread.gmane.org/gmane.comp.standards.posix.austin.general/1925 +stdin: + X=a Y=b; X=$Y Y=$X; echo 1 $X $Y . +expected-stdout: + 1 b b . +--- +name: xxx-multi-assignment-posix-subassign +description: + Check that the behaviour for multiple assignments matches POSIX: + - The assignment words shall be expanded in the current execution + environment. + - The assignments happen in the temporary execution environment. +stdin: + unset X Y Z + Z=a Y=${X:=b} sh -c 'echo +$X+ +$Y+ +$Z+' + echo /$X/ + # Now for the special case: + unset X Y Z + X= Y=${X:=b} sh -c 'echo +$X+ +$Y+' + echo /$X/ +expected-stdout: + ++ +b+ +a+ + /b/ + ++ +b+ + /b/ +--- +name: xxx-exec-environment-1 +description: + Check to see if exec sets it's environment correctly +stdin: + FOO=bar exec env +expected-stdout-pattern: + /(^|.*\n)FOO=bar\n/ +--- +name: xxx-exec-environment-2 +description: + Check to make sure exec doesn't change environment if a program + isn't exec-ed +stdin: + sortprog=$(whence -p sort) || sortprog=cat + env | $sortprog | grep -v '^RANDOM=' >bar1 + FOO=bar exec; env | $sortprog | grep -v '^RANDOM=' >bar2 + cmp -s bar1 bar2 +--- +name: exec-function-environment-1 +description: + Check assignments in function calls and whether they affect + the current execution environment (ksh93, SUSv4) +stdin: + f() { a=2; }; g() { b=3; echo y$c-; }; a=1 f; b=2; c=1 g + echo x$a-$b- z$c- +expected-stdout: + y1- + x2-3- z1- +--- +name: xxx-what-do-you-call-this-1 +stdin: + echo "${foo:-"a"}*" +expected-stdout: + a* +--- +name: xxx-prefix-strip-1 +stdin: + foo='a cdef' + echo ${foo#a c} +expected-stdout: + def +--- +name: xxx-prefix-strip-2 +stdin: + set a c + x='a cdef' + echo ${x#$*} +expected-stdout: + def +--- +name: xxx-variable-syntax-1 +stdin: + echo ${:} +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: xxx-variable-syntax-2 +stdin: + set 0 + echo ${*:0} +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: xxx-variable-syntax-3 +stdin: + set -A foo 0 + echo ${foo[*]:0} +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: xxx-substitution-eval-order +description: + Check order of evaluation of expressions +stdin: + i=1 x= y= + set -A A abc def GHI j G k + echo ${A[x=(i+=1)]#${A[y=(i+=2)]}} + echo $x $y +expected-stdout: + HI + 2 4 +--- +name: xxx-set-option-1 +description: + Check option parsing in set +stdin: + set -vsA foo -- A 1 3 2 + echo ${foo[*]} +expected-stderr: + echo ${foo[*]} +expected-stdout: + 1 2 3 A +--- +name: xxx-exec-1 +description: + Check that exec exits for built-ins +arguments: !-i! +stdin: + exec echo hi + echo still herre +expected-stdout: + hi +expected-stderr-pattern: /.*/ +--- +name: xxx-while-1 +description: + Check the return value of while loops + XXX need to do same for for/select/until loops +stdin: + i=x + while [ $i != xxx ] ; do + i=x$i + if [ $i = xxx ] ; then + false + continue + fi + done + echo loop1=$? + + i=x + while [ $i != xxx ] ; do + i=x$i + if [ $i = xxx ] ; then + false + break + fi + done + echo loop2=$? + + i=x + while [ $i != xxx ] ; do + i=x$i + false + done + echo loop3=$? +expected-stdout: + loop1=0 + loop2=0 + loop3=1 +--- +name: xxx-status-1 +description: + Check that blank lines don't clear $? +arguments: !-i! +stdin: + (exit 1) + echo $? + (exit 1) + + echo $? + true +expected-stdout: + 1 + 1 +expected-stderr-pattern: /.*/ +--- +name: xxx-status-2 +description: + Check that $? is preserved in subshells, includes, traps. +stdin: + (exit 1) + + echo blank: $? + + (exit 2) + (echo subshell: $?) + + echo 'echo include: $?' > foo + (exit 3) + . ./foo + + trap 'echo trap: $?' ERR + (exit 4) + echo exit: $? +expected-stdout: + blank: 1 + subshell: 2 + include: 3 + trap: 4 + exit: 4 +--- +name: xxx-clean-chars-1 +description: + Check MAGIC character is stuffed correctly +stdin: + echo `echo [` +expected-stdout: + [ +--- +name: xxx-param-subst-qmark-1 +description: + Check suppresion of error message with null string. According to + POSIX, it shouldn't print the error as 'word' isn't ommitted. + ksh88/93, Solaris /bin/sh and /usr/xpg4/bin/sh all print the error, + that's why the condition is reversed. +stdin: + unset foo + x= + echo x${foo?$x} +expected-exit: 1 +# POSIX +#expected-fail: yes +#expected-stderr-pattern: !/not set/ +# common use +expected-stderr-pattern: /parameter null or not set/ +--- +name: xxx-param-_-1 +# fails due to weirdness of execv stuff +category: !os:uwin-nt +description: + Check c flag is set. +arguments: !-c!echo "[$-]"! +expected-stdout-pattern: /^\[.*c.*\]$/ +--- +name: tilde-expand-1 +description: + Check tilde expansion after equal signs +env-setup: !HOME=/sweet! +stdin: + echo ${A=a=}~ b=~ c=d~ ~ + set +o braceexpand + echo ${A=a=}~ b=~ c=d~ ~ +expected-stdout: + a=/sweet b=/sweet c=d~ /sweet + a=~ b=~ c=d~ /sweet +--- +name: exit-err-1 +description: + Check some "exit on error" conditions +stdin: + set -ex + /usr/bin/env false && echo something + echo END +expected-stdout: + END +expected-stderr: + + /usr/bin/env false + + echo END +--- +name: exit-err-2 +description: + Check some "exit on error" edge conditions (POSIXly) +stdin: + set -ex + if /usr/bin/env true; then + /usr/bin/env false && echo something + fi + echo END +expected-stdout: + END +expected-stderr: + + /usr/bin/env true + + /usr/bin/env false + + echo END +--- +name: exit-err-3 +description: + pdksh regression which AT&T ksh does right + TFM says: [set] -e | errexit + Exit (after executing the ERR trap) ... +stdin: + trap 'echo EXIT' EXIT + trap 'echo ERR' ERR + set -e + cd /XXXXX 2>/dev/null + echo DONE + exit 0 +expected-stdout: + ERR + EXIT +expected-exit: e != 0 +--- +name: exit-err-4 +description: + "set -e" test suite (POSIX) +stdin: + set -e + echo pre + if true ; then + false && echo foo + fi + echo bar +expected-stdout: + pre + bar +--- +name: exit-err-5 +description: + "set -e" test suite (POSIX) +stdin: + set -e + foo() { + while [ "$1" ]; do + for E in $x; do + [ "$1" = "$E" ] && { shift ; continue 2 ; } + done + x="$x $1" + shift + done + echo $x + } + echo pre + foo a b b c + echo post +expected-stdout: + pre + a b c + post +--- +name: exit-err-6 +description: + "set -e" test suite (BSD make) +category: os:mirbsd +stdin: + mkdir zd zd/a zd/b + print 'all:\n\t@echo eins\n\t@exit 42\n' >zd/a/Makefile + print 'all:\n\t@echo zwei\n' >zd/b/Makefile + wd=$(pwd) + set -e + for entry in a b; do ( set -e; if [[ -d $wd/zd/$entry.i386 ]]; then _newdir_="$entry.i386"; else _newdir_="$entry"; fi; if [[ -z $_THISDIR_ ]]; then _nextdir_="$_newdir_"; else _nextdir_="$_THISDIR_/$_newdir_"; fi; _makefile_spec_=; [[ ! -f $wd/zd/$_newdir_/Makefile.bsd-wrapper ]] || _makefile_spec_="-f Makefile.bsd-wrapper"; subskipdir=; for skipdir in ; do subentry=${skipdir#$entry}; if [[ $subentry != $skipdir ]]; then if [[ -z $subentry ]]; then echo "($_nextdir_ skipped)"; break; fi; subskipdir="$subskipdir ${subentry#/}"; fi; done; if [[ -z $skipdir || -n $subentry ]]; then echo "===> $_nextdir_"; cd $wd/zd/$_newdir_; make SKIPDIR="$subskipdir" $_makefile_spec_ _THISDIR_="$_nextdir_" all; fi; ) done 2>&1 | sed "s!$wd!WD!g" +expected-stdout: + ===> a + eins + *** Error code 42 + + Stop in WD/zd/a (line 2 of Makefile). +--- +name: exit-enoent-1 +description: + SUSv4 says that the shell should exit with 126/127 in some situations +stdin: + i=0 + (echo; echo :) >x + "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . + "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . + echo exit 42 >x + "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . + "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . + rm -f x + "$__progname" ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . + "$__progname" -c ./x >/dev/null 2>&1; r=$?; echo $((i++)) $r . +expected-stdout: + 0 0 . + 1 126 . + 2 42 . + 3 126 . + 4 127 . + 5 127 . +--- +name: exit-eval-1 +description: + Check eval vs substitution exit codes (ksh93 alike) +stdin: + eval $(false) + echo A $? + eval ' $(false)' + echo B $? + eval " $(false)" + echo C $? + eval "eval $(false)" + echo D $? + eval 'eval '"$(false)" + echo E $? + IFS="$IFS:" + eval $(echo :; false) + echo F $? +expected-stdout: + A 0 + B 1 + C 0 + D 0 + E 0 + F 0 +--- +name: test-stlt-1 +description: + Check that test also can handle string1 < string2 etc. +stdin: + test 2005/10/08 '<' 2005/08/21 && echo ja || echo nein + test 2005/08/21 \< 2005/10/08 && echo ja || echo nein + test 2005/10/08 '>' 2005/08/21 && echo ja || echo nein + test 2005/08/21 \> 2005/10/08 && echo ja || echo nein +expected-stdout: + nein + ja + ja + nein +expected-stderr-pattern: !/unexpected op/ +--- +name: test-precedence-1 +description: + Check a weird precedence case (and POSIX echo) +stdin: + test \( -f = -f \) + rv=$? + test -n "$POSH_VERSION" || set -o sh + echo -e $rv +expected-stdout: + -e 0 +--- +name: test-option-1 +description: + Test the test -o operator +stdin: + runtest() { + test -o $1; echo $? + [ -o $1 ]; echo $? + [[ -o $1 ]]; echo $? + } + if_test() { + test -o $1 -o -o !$1; echo $? + [ -o $1 -o -o !$1 ]; echo $? + [[ -o $1 || -o !$1 ]]; echo $? + test -o ?$1; echo $? + } + echo 0y $(if_test utf8-mode) = + echo 0n $(if_test utf8-hack) = + echo 1= $(runtest utf8-hack) = + echo 2= $(runtest !utf8-hack) = + echo 3= $(runtest ?utf8-hack) = + set +U + echo 1+ $(runtest utf8-mode) = + echo 2+ $(runtest !utf8-mode) = + echo 3+ $(runtest ?utf8-mode) = + set -U + echo 1- $(runtest utf8-mode) = + echo 2- $(runtest !utf8-mode) = + echo 3- $(runtest ?utf8-mode) = + echo = short flags = + echo 0y $(if_test -U) = + echo 0y $(if_test +U) = + echo 0n $(if_test -_) = + echo 0n $(if_test -U-) = + echo 1= $(runtest -_) = + echo 2= $(runtest !-_) = + echo 3= $(runtest ?-_) = + set +U + echo 1+ $(runtest -U) = + echo 2+ $(runtest !-U) = + echo 3+ $(runtest ?-U) = + echo 1+ $(runtest +U) = + echo 2+ $(runtest !+U) = + echo 3+ $(runtest ?+U) = + set -U + echo 1- $(runtest -U) = + echo 2- $(runtest !-U) = + echo 3- $(runtest ?-U) = + echo 1- $(runtest +U) = + echo 2- $(runtest !+U) = + echo 3- $(runtest ?+U) = +expected-stdout: + 0y 0 0 0 0 = + 0n 1 1 1 1 = + 1= 1 1 1 = + 2= 1 1 1 = + 3= 1 1 1 = + 1+ 1 1 1 = + 2+ 0 0 0 = + 3+ 0 0 0 = + 1- 0 0 0 = + 2- 1 1 1 = + 3- 0 0 0 = + = short flags = + 0y 0 0 0 0 = + 0y 0 0 0 0 = + 0n 1 1 1 1 = + 0n 1 1 1 1 = + 1= 1 1 1 = + 2= 1 1 1 = + 3= 1 1 1 = + 1+ 1 1 1 = + 2+ 0 0 0 = + 3+ 0 0 0 = + 1+ 1 1 1 = + 2+ 0 0 0 = + 3+ 0 0 0 = + 1- 0 0 0 = + 2- 1 1 1 = + 3- 0 0 0 = + 1- 0 0 0 = + 2- 1 1 1 = + 3- 0 0 0 = +--- +name: mkshrc-1 +description: + Check that ~/.mkshrc works correctly. + Part 1: verify user environment is not read (internal) +stdin: + echo x $FNORD +expected-stdout: + x +--- +name: mkshrc-2a +description: + Check that ~/.mkshrc works correctly. + Part 2: verify mkshrc is not read (non-interactive shells) +file-setup: file 644 ".mkshrc" + FNORD=42 +env-setup: !HOME=.!ENV=! +stdin: + echo x $FNORD +expected-stdout: + x +--- +name: mkshrc-2b +description: + Check that ~/.mkshrc works correctly. + Part 2: verify mkshrc can be read (interactive shells) +file-setup: file 644 ".mkshrc" + FNORD=42 +arguments: !-i! +env-setup: !HOME=.!ENV=!PS1=! +stdin: + echo x $FNORD +expected-stdout: + x 42 +expected-stderr-pattern: + /(# )*/ +--- +name: mkshrc-3 +description: + Check that ~/.mkshrc works correctly. + Part 3: verify mkshrc can be turned off +file-setup: file 644 ".mkshrc" + FNORD=42 +env-setup: !HOME=.!ENV=nonexistant! +stdin: + echo x $FNORD +expected-stdout: + x +--- +name: sh-mode-1 +description: + Check that sh mode turns braceexpand off + and that that works correctly +stdin: + set -o braceexpand + set +o sh + [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh + [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex + echo {a,b,c} + set +o braceexpand + echo {a,b,c} + set -o braceexpand + echo {a,b,c} + set -o sh + echo {a,b,c} + [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh + [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex + set -o braceexpand + echo {a,b,c} + [[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh + [[ $(set +o) == *@(-o braceexpand)@(| *) ]] && echo brex || echo nobrex +expected-stdout: + nosh + brex + a b c + {a,b,c} + a b c + {a,b,c} + sh + nobrex + a b c + sh + brex +--- +name: sh-mode-2a +description: + Check that sh mode is *not* automatically turned on +category: !binsh +stdin: + ln -s "$__progname" ksh + ln -s "$__progname" sh + ln -s "$__progname" ./-ksh + ln -s "$__progname" ./-sh + for shell in {,-}{,k}sh; do + print -- $shell $(./$shell +l -c \ + '[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh') + done +expected-stdout: + sh nosh + ksh nosh + -sh nosh + -ksh nosh +--- +name: sh-mode-2b +description: + Check that sh mode *is* automatically turned on +category: binsh +stdin: + ln -s "$__progname" ksh + ln -s "$__progname" sh + ln -s "$__progname" ./-ksh + ln -s "$__progname" ./-sh + for shell in {,-}{,k}sh; do + print -- $shell $(./$shell +l -c \ + '[[ $(set +o) == *@(-o sh)@(| *) ]] && echo sh || echo nosh') + done +expected-stdout: + sh sh + ksh nosh + -sh sh + -ksh nosh +--- +name: pipeline-1 +description: + pdksh bug: last command of a pipeline is executed in a + subshell - make sure it still is, scripts depend on it +file-setup: file 644 "abcx" +file-setup: file 644 "abcy" +stdin: + echo * + echo a | while read d; do + echo $d + echo $d* + echo * + set -o noglob + echo $d* + echo * + done + echo * +expected-stdout: + abcx abcy + a + abcx abcy + abcx abcy + a* + * + abcx abcy +--- +name: pipeline-2 +description: + check that co-processes work with TCOMs, TPIPEs and TPARENs +stdin: + "$__progname" -c 'i=100; echo hi |& while read -p line; do echo "$((i++)) $line"; done' + "$__progname" -c 'i=200; echo hi | cat |& while read -p line; do echo "$((i++)) $line"; done' + "$__progname" -c 'i=300; (echo hi | cat) |& while read -p line; do echo "$((i++)) $line"; done' +expected-stdout: + 100 hi + 200 hi + 300 hi +--- +name: persist-history-1 +description: + Check if persistent history saving works +category: !no-histfile +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + cat hist.file +expected-stdout-pattern: + /cat hist.file/ +expected-stderr-pattern: + /^X*$/ +--- +name: typeset-padding-1 +description: + Check if left/right justification works as per TFM +stdin: + typeset -L10 ln=0hall0 + typeset -R10 rn=0hall0 + typeset -ZL10 lz=0hall0 + typeset -ZR10 rz=0hall0 + typeset -Z10 rx=" hallo " + echo "<$ln> <$rn> <$lz> <$rz> <$rx>" +expected-stdout: + <0hall0 > < 0hall0> <hall0 > <00000hall0> <0000 hallo> +--- +name: typeset-padding-2 +description: + Check if base-!10 integers are padded right +stdin: + typeset -Uui16 -L9 ln=16#1 + typeset -Uui16 -R9 rn=16#1 + typeset -Uui16 -Z9 zn=16#1 + typeset -L9 ls=16#1 + typeset -R9 rs=16#1 + typeset -Z9 zs=16#1 + echo "<$ln> <$rn> <$zn> <$ls> <$rs> <$zs>" +expected-stdout: + <16#1 > < 16#1> <16#000001> <16#1 > < 16#1> <0000016#1> +--- +name: utf8bom-1 +description: + Check that the UTF-8 Byte Order Mark is ignored as the first + multibyte character of the shell input (with -c, from standard + input, as file, or as eval argument), but nowhere else +# breaks on Mac OSX (HFS+ non-standard Unicode canonical decomposition) +category: !os:darwin +stdin: + mkdir foo + print '#!/bin/sh\necho ohne' >foo/fnord + print '#!/bin/sh\necho mit' >foo/fnord + print 'fnord\nfnord\nfnord\nfnord' >foo/bar + print eval \''fnord\nfnord\nfnord\nfnord'\' >foo/zoo + set -A anzahl -- foo/* + echo got ${#anzahl[*]} files + chmod +x foo/* + export PATH=$(pwd)/foo:$PATH + "$__progname" -c 'fnord' + echo = + "$__progname" -c 'fnord; fnord; fnord; fnord' + echo = + "$__progname" foo/bar + echo = + "$__progname" <foo/bar + echo = + "$__progname" foo/zoo + echo = + "$__progname" -c 'echo : $(fnord)' + rm -rf foo +expected-stdout: + got 4 files + ohne + = + ohne + ohne + mit + ohne + = + ohne + ohne + mit + ohne + = + ohne + ohne + mit + ohne + = + ohne + ohne + mit + ohne + = + : mit +--- +name: utf8bom-2 +description: + Check that we can execute BOM-shebangs (failures not fatal) + XXX if the OS can already execute them, we lose + note: cygwin execve(2) doesn't return to us with ENOEXEC, we lose + note: Ultrix perl5 t4 returns 65280 (exit-code 255) and no text +category: !os:cygwin,!os:uwin-nt,!os:ultrix,!smksh +env-setup: !FOO=BAR! +stdin: + print '#!'"$__progname"'\nprint "1 a=$ENV{FOO}";' >t1 + print '#!'"$__progname"'\nprint "2 a=$ENV{FOO}";' >t2 + print '#!'"$__perlname"'\nprint "3 a=$ENV{FOO}\n";' >t3 + print '#!'"$__perlname"'\nprint "4 a=$ENV{FOO}\n";' >t4 + chmod +x t? + ./t1 + ./t2 + ./t3 + ./t4 +expected-stdout: + 1 a=/nonexistant{FOO} + 2 a=/nonexistant{FOO} + 3 a=BAR + 4 a=BAR +expected-stderr-pattern: + /(Unrecognized character .... ignored at \..t4 line 1)*/ +--- +name: utf8bom-3 +description: + Reading the UTF-8 BOM should enable the utf8-mode flag +stdin: + "$__progname" -c ':; if [[ $- = *U* ]]; then echo 1 on; else echo 1 off; fi' + "$__progname" -c ':; if [[ $- = *U* ]]; then echo 2 on; else echo 2 off; fi' +expected-stdout: + 1 off + 2 on +--- +name: utf8opt-1a +description: + Check that the utf8-mode flag is not set at non-interactive startup +category: !os:hpux +env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8! +stdin: + if [[ $- = *U* ]]; then + echo is set + else + echo is not set + fi +expected-stdout: + is not set +--- +name: utf8opt-1b +description: + Check that the utf8-mode flag is not set at non-interactive startup +category: os:hpux +env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8! +stdin: + if [[ $- = *U* ]]; then + echo is set + else + echo is not set + fi +expected-stdout: + is not set +--- +name: utf8opt-2a +description: + Check that the utf8-mode flag is set at interactive startup. + -DMKSH_ASSUME_UTF8=0 => expected failure, please ignore + -DMKSH_ASSUME_UTF8=1 => not expected, please investigate + -UMKSH_ASSUME_UTF8 => not expected, but if your OS is old, + try passing HAVE_SETLOCALE_CTYPE=0 to Build.sh +category: !os:hpux +arguments: !-i! +env-setup: !PS1=!PS2=!LC_CTYPE=en_US.UTF-8! +stdin: + if [[ $- = *U* ]]; then + echo is set + else + echo is not set + fi +expected-stdout: + is set +expected-stderr-pattern: + /(# )*/ +--- +name: utf8opt-2b +description: + Check that the utf8-mode flag is set at interactive startup + Expected failure if -DMKSH_ASSUME_UTF8=0 +category: os:hpux +arguments: !-i! +env-setup: !PS1=!PS2=!LC_CTYPE=en_US.utf8! +stdin: + if [[ $- = *U* ]]; then + echo is set + else + echo is not set + fi +expected-stdout: + is set +expected-stderr-pattern: + /(# )*/ +--- +name: utf8opt-3 +description: + Ensure ±U on the command line is honoured + (this test may pass falsely depending on CPPFLAGS) +stdin: + export i=0 + code='if [[ $- = *U* ]]; then echo $i on; else echo $i off; fi' + let i++; "$__progname" -U -c "$code" + let i++; "$__progname" +U -c "$code" + let i++; "$__progname" -U -ic "$code" + let i++; "$__progname" +U -ic "$code" + echo $((++i)) done +expected-stdout: + 1 on + 2 off + 3 on + 4 off + 5 done +--- +name: aliases-1 +description: + Check if built-in shell aliases are okay +category: !arge +stdin: + alias + typeset -f +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + suspend='kill -STOP $$' + type='whence -v' +--- +name: aliases-1-hartz4 +description: + Check if built-in shell aliases are okay +category: arge +stdin: + alias + typeset -f +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + type='whence -v' +--- +name: aliases-2a +description: + Check if “set -o sh” disables built-in aliases (except a few) +category: disabled +arguments: !-o!sh! +stdin: + alias + typeset -f +expected-stdout: + integer='typeset -i' + local=typeset +--- +name: aliases-3a +description: + Check if running as sh disables built-in aliases (except a few) +category: disabled +arguments: !-o!sh! +stdin: + cp "$__progname" sh + ./sh -c 'alias; typeset -f' + rm -f sh +expected-stdout: + integer='typeset -i' + local=typeset +--- +name: aliases-2b +description: + Check if “set -o sh” does not influence built-in aliases +category: !arge +arguments: !-o!sh! +stdin: + alias + typeset -f +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + suspend='kill -STOP $$' + type='whence -v' +--- +name: aliases-3b +description: + Check if running as sh does not influence built-in aliases +category: !arge +arguments: !-o!sh! +stdin: + cp "$__progname" sh + ./sh -c 'alias; typeset -f' + rm -f sh +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + suspend='kill -STOP $$' + type='whence -v' +--- +name: aliases-2b-hartz4 +description: + Check if “set -o sh” does not influence built-in aliases +category: arge +arguments: !-o!sh! +stdin: + alias + typeset -f +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + type='whence -v' +--- +name: aliases-3b-hartz4 +description: + Check if running as sh does not influence built-in aliases +category: arge +arguments: !-o!sh! +stdin: + cp "$__progname" sh + ./sh -c 'alias; typeset -f' + rm -f sh +expected-stdout: + autoload='typeset -fu' + functions='typeset -f' + hash='alias -t' + history='fc -l' + integer='typeset -i' + local=typeset + login='exec login' + nameref='typeset -n' + nohup='nohup ' + r='fc -e -' + source='PATH=$PATH:. command .' + type='whence -v' +--- +name: aliases-funcdef-1 +description: + Check if POSIX functions take precedences over aliases +stdin: + alias foo='echo makro' + foo() { + echo funktion + } + foo +expected-stdout: + funktion +--- +name: aliases-funcdef-2 +description: + Check if POSIX functions take precedences over aliases +stdin: + alias foo='echo makro' + foo () { + echo funktion + } + foo +expected-stdout: + funktion +--- +name: aliases-funcdef-3 +description: + Check if aliases take precedences over Korn functions +stdin: + alias foo='echo makro' + function foo { + echo funktion + } + foo +expected-stdout: + makro +--- +name: arrays-1 +description: + Check if Korn Shell arrays work as expected +stdin: + v="c d" + set -A foo -- a \$v "$v" '$v' b + echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|" +expected-stdout: + 5|a|$v|c d|$v|b| +--- +name: arrays-2 +description: + Check if bash-style arrays work as expected +category: !smksh +stdin: + v="c d" + foo=(a \$v "$v" '$v' b) + echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|" +expected-stdout: + 5|a|$v|c d|$v|b| +--- +name: arrays-3 +description: + Check if array bounds are uint32_t +stdin: + set -A foo a b c + foo[4097]=d + foo[2147483637]=e + echo ${foo[*]} + foo[-1]=f + echo ${foo[4294967295]} g ${foo[*]} +expected-stdout: + a b c d e + f g a b c d e f +--- +name: arrays-4 +description: + Check if Korn Shell arrays with specified indices work as expected +category: !smksh +stdin: + v="c d" + set -A foo -- [1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b + echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|" +expected-stdout: + 5|a|$v|c d||$v|b| +--- +name: arrays-5 +description: + Check if bash-style arrays with specified indices work as expected +category: !smksh +stdin: + v="c d" + foo=([1]=\$v [2]="$v" [4]='$v' [0]=a [5]=b) + echo "${#foo[*]}|${foo[0]}|${foo[1]}|${foo[2]}|${foo[3]}|${foo[4]}|${foo[5]}|" + x=([128]=foo bar baz) + echo k= ${!x[*]} . + echo v= ${x[*]} . +expected-stdout: + 5|a|$v|c d||$v|b| + k= 128 129 130 . + v= foo bar baz . +--- +name: arrays-6 +description: + Check if we can get the array keys (indices) for indexed arrays, + Korn shell style +stdin: + of() { + i=0 + for x in "$@"; do + echo -n "$((i++))<$x>" + done + echo + } + foo[1]=eins + set | grep '^foo' + echo = + foo[0]=zwei + foo[4]=drei + set | grep '^foo' + echo = + echo a $(of ${foo[*]}) = $(of ${bar[*]}) a + echo b $(of "${foo[*]}") = $(of "${bar[*]}") b + echo c $(of ${foo[@]}) = $(of ${bar[@]}) c + echo d $(of "${foo[@]}") = $(of "${bar[@]}") d + echo e $(of ${!foo[*]}) = $(of ${!bar[*]}) e + echo f $(of "${!foo[*]}") = $(of "${!bar[*]}") f + echo g $(of ${!foo[@]}) = $(of ${!bar[@]}) g + echo h $(of "${!foo[@]}") = $(of "${!bar[@]}") h +expected-stdout: + foo[1]=eins + = + foo[0]=zwei + foo[1]=eins + foo[4]=drei + = + a 0<zwei>1<eins>2<drei> = a + b 0<zwei eins drei> = 0<> b + c 0<zwei>1<eins>2<drei> = c + d 0<zwei>1<eins>2<drei> = d + e 0<0>1<1>2<4> = e + f 0<0 1 4> = 0<> f + g 0<0>1<1>2<4> = g + h 0<0>1<1>2<4> = h +--- +name: arrays-7 +description: + Check if we can get the array keys (indices) for indexed arrays, + Korn shell style, in some corner cases +stdin: + echo !arz: ${!arz} + echo !arz[0]: ${!arz[0]} + echo !arz[1]: ${!arz[1]} + arz=foo + echo !arz: ${!arz} + echo !arz[0]: ${!arz[0]} + echo !arz[1]: ${!arz[1]} + unset arz + echo !arz: ${!arz} + echo !arz[0]: ${!arz[0]} + echo !arz[1]: ${!arz[1]} +expected-stdout: + !arz: 0 + !arz[0]: + !arz[1]: + !arz: arz + !arz[0]: 0 + !arz[1]: + !arz: 0 + !arz[0]: + !arz[1]: +--- +name: arrays-8 +description: + Check some behavioural rules for arrays. +stdin: + fna() { + set -A aa 9 + } + fnb() { + typeset ab + set -A ab 9 + } + fnc() { + typeset ac + set -A ac 91 + unset ac + set -A ac 92 + } + fnd() { + set +A ad 9 + } + fne() { + unset ae + set +A ae 9 + } + fnf() { + unset af[0] + set +A af 9 + } + fng() { + unset ag[*] + set +A ag 9 + } + set -A aa 1 2 + set -A ab 1 2 + set -A ac 1 2 + set -A ad 1 2 + set -A ae 1 2 + set -A af 1 2 + set -A ag 1 2 + set -A ah 1 2 + typeset -Z3 aa ab ac ad ae af ag + print 1a ${aa[*]} . + print 1b ${ab[*]} . + print 1c ${ac[*]} . + print 1d ${ad[*]} . + print 1e ${ae[*]} . + print 1f ${af[*]} . + print 1g ${ag[*]} . + print 1h ${ah[*]} . + fna + fnb + fnc + fnd + fne + fnf + fng + typeset -Z5 ah[*] + print 2a ${aa[*]} . + print 2b ${ab[*]} . + print 2c ${ac[*]} . + print 2d ${ad[*]} . + print 2e ${ae[*]} . + print 2f ${af[*]} . + print 2g ${ag[*]} . + print 2h ${ah[*]} . +expected-stdout: + 1a 001 002 . + 1b 001 002 . + 1c 001 002 . + 1d 001 002 . + 1e 001 002 . + 1f 001 002 . + 1g 001 002 . + 1h 1 2 . + 2a 9 . + 2b 001 002 . + 2c 92 . + 2d 009 002 . + 2e 9 . + 2f 9 002 . + 2g 009 . + 2h 00001 00002 . +--- +name: varexpand-substr-1 +description: + Check if bash-style substring expansion works + when using positive numerics +stdin: + x=abcdefghi + typeset -i y=123456789 + typeset -i 16 z=123456789 # 16#75bcd15 + echo a t${x:2:2} ${y:2:3} ${z:2:3} a + echo b ${x::3} ${y::3} ${z::3} b + echo c ${x:2:} ${y:2:} ${z:2:} c + echo d ${x:2} ${y:2} ${z:2} d + echo e ${x:2:6} ${y:2:6} ${z:2:7} e + echo f ${x:2:7} ${y:2:7} ${z:2:8} f + echo g ${x:2:8} ${y:2:8} ${z:2:9} g +expected-stdout: + a tcd 345 #75 a + b abc 123 16# b + c c + d cdefghi 3456789 #75bcd15 d + e cdefgh 345678 #75bcd1 e + f cdefghi 3456789 #75bcd15 f + g cdefghi 3456789 #75bcd15 g +--- +name: varexpand-substr-2 +description: + Check if bash-style substring expansion works + when using negative numerics or expressions +stdin: + x=abcdefghi + typeset -i y=123456789 + typeset -i 16 z=123456789 # 16#75bcd15 + n=2 + echo a ${x:$n:3} ${y:$n:3} ${z:$n:3} a + echo b ${x:(n):3} ${y:(n):3} ${z:(n):3} b + echo c ${x:(-2):1} ${y:(-2):1} ${z:(-2):1} c + echo d t${x: n:2} ${y: n:3} ${z: n:3} d +expected-stdout: + a cde 345 #75 a + b cde 345 #75 b + c h 8 1 c + d tcd 345 #75 d +--- +name: varexpand-substr-3 +description: + Check that some things that work in bash fail. + This is by design. And that some things fail in both. +stdin: + export x=abcdefghi n=2 + "$__progname" -c 'echo v${x:(n)}x' + "$__progname" -c 'echo w${x: n}x' + "$__progname" -c 'echo x${x:n}x' + "$__progname" -c 'echo y${x:}x' + "$__progname" -c 'echo z${x}x' + "$__progname" -c 'x=abcdef;y=123;echo ${x:${y:2:1}:2}' >/dev/null 2>&1; echo $? +expected-stdout: + vcdefghix + wcdefghix + zabcdefghix + 1 +expected-stderr-pattern: + /x:n.*bad substitution.*\n.*bad substitution/ +--- +name: varexpand-substr-4 +description: + Check corner cases for substring expansion +stdin: + x=abcdefghi + integer y=2 + echo a ${x:(y == 1 ? 2 : 3):4} a +expected-stdout: + a defg a +--- +name: varexpand-substr-5A +description: + Check that substring expansions work on characters +stdin: + set +U + x=mäh + echo a ${x::1} ${x: -1} a + echo b ${x::3} ${x: -3} b + echo c ${x:1:2} ${x: -3:2} c + echo d ${#x} d +expected-stdout: + a m h a + b mä äh b + c ä ä c + d 4 d +--- +name: varexpand-substr-5W +description: + Check that substring expansions work on characters +stdin: + set -U + x=mäh + echo a ${x::1} ${x: -1} a + echo b ${x::2} ${x: -2} b + echo c ${x:1:1} ${x: -2:1} c + echo d ${#x} d +expected-stdout: + a m h a + b mä äh b + c ä ä c + d 3 d +--- +name: varexpand-substr-6 +description: + Check that string substitution works correctly +stdin: + foo=1 + bar=2 + baz=qwertyuiop + echo a ${baz: foo: bar} + echo b ${baz: foo: $bar} + echo c ${baz: $foo: bar} + echo d ${baz: $foo: $bar} +expected-stdout: + a we + b we + c we + d we +--- +name: varexpand-null-1 +description: + Ensure empty strings expand emptily +stdin: + print x ${a} ${b} y + print z ${a#?} ${b%?} w + print v ${a=} ${b/c/d} u +expected-stdout: + x y + z w + v u +--- +name: varexpand-null-2 +description: + Ensure empty strings, when quoted, are expanded as empty strings +stdin: + printf '<%s> ' 1 "${a}" 2 "${a#?}" + "${b%?}" 3 "${a=}" + "${b/c/d}" + echo . +expected-stdout: + <1> <> <2> <> <+> <> <3> <> <+> <> . +--- +name: print-funny-chars +description: + Check print builtin's capability to output designated characters +stdin: + print '<\0144\0344\xDB\u00DB\u20AC\uDB\x40>' +expected-stdout: + <dÛ€Û@> +--- +name: print-bksl-c +description: + Check print builtin's \c escape +stdin: + print '\ca'; print b +expected-stdout: + ab +--- +name: print-nul-chars +description: + Check handling of NUL characters for print and read + note: second line should output “4 3” but we cannot + handle NUL characters in strings yet +stdin: + print $(($(print '<\0>' | wc -c))) + x=$(print '<\0>') + print $(($(print "$x" | wc -c))) ${#x} +expected-stdout: + 4 + 3 2 +--- +name: print-escapes +description: + Check backslash expansion by the print builtin +stdin: + print '\ \!\"\#\$\%\&'\\\''\(\)\*\+\,\-\.\/\0\1\2\3\4\5\6\7\8' \ + '\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T' \ + '\U\V\W\X\Y\Z\[\\\]\^\_\`\a\b \d\e\f\g\h\i\j\k\l\m\n\o\p' \ + '\q\r\s\t\u\v\w\x\y\z\{\|\}\~' '\u20acd' '\U20acd' '\x123' \ + '\0x' '\0123' '\01234' | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z5 hv + typeset -i1 wc=0x0A + dasc= + nl=${wc#1#} + while IFS= read -r line; do + line=$line$nl + while [[ -n $line ]]; do + hv=1#${line::1} + if (( (pos & 15) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc=$dasc. + else + dasc=$dasc${line::1} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + line=${line:1} + done + done + if (( (pos & 15) != 1 )); then + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 5C 20 5C 21 5C 22 5C 23 - 5C 24 5C 25 5C 26 5C 27 |\ \!\"\#\$\%\&\'| + 00000010 5C 28 5C 29 5C 2A 5C 2B - 5C 2C 5C 2D 5C 2E 5C 2F |\(\)\*\+\,\-\.\/| + 00000020 5C 31 5C 32 5C 33 5C 34 - 5C 35 5C 36 5C 37 5C 38 |\1\2\3\4\5\6\7\8| + 00000030 20 5C 39 5C 3A 5C 3B 5C - 3C 5C 3D 5C 3E 5C 3F 5C | \9\:\;\<\=\>\?\| + 00000040 40 5C 41 5C 42 5C 43 5C - 44 1B 5C 46 5C 47 5C 48 |@\A\B\C\D.\F\G\H| + 00000050 5C 49 5C 4A 5C 4B 5C 4C - 5C 4D 5C 4E 5C 4F 5C 50 |\I\J\K\L\M\N\O\P| + 00000060 5C 51 5C 52 5C 53 5C 54 - 20 5C 56 5C 57 5C 58 5C |\Q\R\S\T \V\W\X\| + 00000070 59 5C 5A 5C 5B 5C 5C 5D - 5C 5E 5C 5F 5C 60 07 08 |Y\Z\[\]\^\_\`..| + 00000080 20 20 5C 64 1B 0C 5C 67 - 5C 68 5C 69 5C 6A 5C 6B | \d..\g\h\i\j\k| + 00000090 5C 6C 5C 6D 0A 5C 6F 5C - 70 20 5C 71 0D 5C 73 09 |\l\m.\o\p \q.\s.| + 000000A0 0B 5C 77 5C 79 5C 7A 5C - 7B 5C 7C 5C 7D 5C 7E 20 |.\w\y\z\{\|\}\~ | + 000000B0 E2 82 AC 64 20 EF BF BD - 20 12 33 20 78 20 53 20 |...d ... .3 x S | + 000000C0 53 34 0A - |S4.| +--- +name: dollar-quoted-strings +description: + Check backslash expansion by $'…' strings +stdin: + printf '%s\n' $'\ \!\"\#\$\%\&\'\(\)\*\+\,\-\.\/ \1\2\3\4\5\6' \ + $'a\0b' $'a\01b' $'\7\8\9\:\;\<\=\>\?\@\A\B\C\D\E\F\G\H\I' \ + $'\J\K\L\M\N\O\P\Q\R\S\T\U1\V\W\X\Y\Z\[\\\]\^\_\`\a\b\d\e' \ + $'\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u1\v\w\x1\y\z\{\|\}\~ $x' \ + $'\u20acd' $'\U20acd' $'\x123' $'fn\x0rd' $'\0234' $'\234' \ + $'\2345' $'\ca' $'\c!' $'\c?' $'\c€' $'a\ + b' | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z5 hv + typeset -i1 wc=0x0A + dasc= + nl=${wc#1#} + while IFS= read -r line; do + line=$line$nl + while [[ -n $line ]]; do + hv=1#${line::1} + if (( (pos & 15) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc=$dasc. + else + dasc=$dasc${line::1} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + line=${line:1} + done + done + if (( (pos & 15) != 1 )); then + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 20 21 22 23 24 25 26 27 - 28 29 2A 2B 2C 2D 2E 2F | !"#$%&'()*+,-./| + 00000010 20 01 02 03 04 05 06 0A - 61 0A 61 01 62 0A 07 38 | .......a.a.b..8| + 00000020 39 3A 3B 3C 3D 3E 3F 40 - 41 42 43 44 1B 46 47 48 |9:;<=>?@ABCD.FGH| + 00000030 49 0A 4A 4B 4C 4D 4E 4F - 50 51 52 53 54 01 56 57 |I.JKLMNOPQRST.VW| + 00000040 58 59 5A 5B 5C 5D 5E 5F - 60 07 08 64 1B 0A 0C 67 |XYZ[\]^_`..d...g| + 00000050 68 69 6A 6B 6C 6D 0A 6F - 70 71 0D 73 09 01 0B 77 |hijklm.opq.s...w| + 00000060 01 79 7A 7B 7C 7D 7E 20 - 24 78 0A E2 82 AC 64 0A |.yz{|}~ $x....d.| + 00000070 EF BF BD 0A C4 A3 0A 66 - 6E 0A 13 34 0A 9C 0A 9C |.......fn..4....| + 00000080 35 0A 01 0A 01 0A 7F 0A - 02 82 AC 0A 61 0A 62 0A |5...........a.b.| +--- +name: dollar-quotes-in-heredocs +description: + They are, however, not parsed in here documents +stdin: + cat <<EOF + dollar = strchr(s, '$'); /* ' */ + EOF + cat <<$'a\tb' + a\tb + a b +expected-stdout: + dollar = strchr(s, '$'); /* ' */ + a\tb +--- +name: dollar-quotes-in-herestrings +description: + They are, not parsed in here strings either +stdin: + cat <<<"dollar = strchr(s, '$'); /* ' */" + cat <<<'dollar = strchr(s, '\''$'\''); /* '\'' */' + x="dollar = strchr(s, '$'); /* ' */" + cat <<<"$x" + cat <<<$'a\E[0m\tb' +expected-stdout: + dollar = strchr(s, '$'); /* ' */ + dollar = strchr(s, '$'); /* ' */ + dollar = strchr(s, '$'); /* ' */ + a[0m b +--- +name: dot-needs-argument +description: + check Debian #415167 solution: '.' without arguments should fail +stdin: + "$__progname" -c . + "$__progname" -c source +expected-exit: e != 0 +expected-stderr-pattern: + /\.: missing argument.*\n.*\.: missing argument/ +--- +name: alias-function-no-conflict +description: + make aliases not conflict with functions + note: for ksh-like functions, the order of preference is + different; bash outputs baz instead of bar in line 2 below +stdin: + alias foo='echo bar' + foo() { + echo baz + } + alias korn='echo bar' + function korn { + echo baz + } + foo + korn + unset -f foo + foo 2>&- || echo rab +expected-stdout: + baz + bar + rab +--- +name: bash-function-parens +description: + ensure the keyword function is ignored when preceding + POSIX style function declarations (bashism) +stdin: + mk() { + echo '#!'"$__progname" + echo "$1 {" + echo ' echo "bar='\''$0'\'\" + echo '}' + echo ${2:-foo} + } + mk 'function foo' >f-korn + mk 'foo ()' >f-dash + mk 'function foo ()' >f-bash + mk 'function stop ()' stop >f-stop + chmod +x f-* + echo "korn: $(./f-korn)" + echo "dash: $(./f-dash)" + echo "bash: $(./f-bash)" + echo "stop: $(./f-stop)" +expected-stdout: + korn: bar='foo' + dash: bar='./f-dash' + bash: bar='./f-bash' + stop: bar='./f-stop' +--- +name: integer-base-one-1 +description: + check if the use of fake integer base 1 works +stdin: + set -U + typeset -Uui16 i0=1# i1=1#€ + typeset -i1 o0a=64 + typeset -i1 o1a=0x263A + typeset -Uui1 o0b=0x7E + typeset -Uui1 o1b=0xFDD0 + integer px=0xCAFE 'p0=1# ' p1=1#… pl=1#f + echo "in <$i0> <$i1>" + echo "out <${o0a#1#}|${o0b#1#}> <${o1a#1#}|${o1b#1#}>" + typeset -Uui1 i0 i1 + echo "pass <$px> <$p0> <$p1> <$pl> <${i0#1#}|${i1#1#}>" + typeset -Uui16 tv1=1#~ tv2=1# tv3=1# tv4=1# tv5=1# tv6=1# tv7=1# tv8=1# + echo "specX <${tv1#16#}> <${tv2#16#}> <${tv3#16#}> <${tv4#16#}> <${tv5#16#}> <${tv6#16#}> <${tv7#16#}> <${tv8#16#}>" + typeset -i1 tv1 tv2 tv3 tv4 tv5 tv6 tv7 tv8 + echo "specW <${tv1#1#}> <${tv2#1#}> <${tv3#1#}> <${tv4#1#}> <${tv5#1#}> <${tv6#1#}> <${tv7#1#}> <${tv8#1#}>" + typeset -i1 xs1=0xEF7F xs2=0xEF80 xs3=0xFDD0 + echo "specU <${xs1#1#}> <${xs2#1#}> <${xs3#1#}>" +expected-stdout: + in <16#EFEF> <16#20AC> + out <@|~> <☺|> + pass <16#cafe> <1# > <1#…> <1#f> <|€> + specX <7E> <7F> <EF80> <EF81> <EFC0> <EFC1> <A0> <80> + specW <~> <> <> <> <> <> < > <> + specU <> <> <> +--- +name: integer-base-one-2a +description: + check if the use of fake integer base 1 stops at correct characters +stdin: + set -U + integer x=1#foo + echo /$x/ +expected-stderr-pattern: + /1#foo: unexpected 'oo'/ +expected-exit: e != 0 +--- +name: integer-base-one-2b +description: + check if the use of fake integer base 1 stops at correct characters +stdin: + set -U + integer x=1# + echo /$x/ +expected-stderr-pattern: + /1#: unexpected ''/ +expected-exit: e != 0 +--- +name: integer-base-one-2c1 +description: + check if the use of fake integer base 1 stops at correct characters +stdin: + set -U + integer x=1#… + echo /$x/ +expected-stdout: + /1#…/ +--- +name: integer-base-one-2c2 +description: + check if the use of fake integer base 1 stops at correct characters +stdin: + set +U + integer x=1#… + echo /$x/ +expected-stderr-pattern: + /1#…: unexpected ''/ +expected-exit: e != 0 +--- +name: integer-base-one-2d1 +description: + check if the use of fake integer base 1 handles octets okay +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # invalid utf-8 +expected-stdout: + /16#efff/ +--- +name: integer-base-one-2d2 +description: + check if the use of fake integer base 1 handles octets +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # invalid 2-byte +expected-stdout: + /16#efc2/ +--- +name: integer-base-one-2d3 +description: + check if the use of fake integer base 1 handles octets +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # invalid 2-byte +expected-stdout: + /16#efef/ +--- +name: integer-base-one-2d4 +description: + check if the use of fake integer base 1 stops at invalid input +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # invalid 3-byte +expected-stderr-pattern: + /1#: unexpected ''/ +expected-exit: e != 0 +--- +name: integer-base-one-2d5 +description: + check if the use of fake integer base 1 stops at invalid input +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # non-minimalistic +expected-stderr-pattern: + /1#: unexpected ''/ +expected-exit: e != 0 +--- +name: integer-base-one-2d6 +description: + check if the use of fake integer base 1 stops at invalid input +stdin: + set -U + typeset -i16 x=1# + echo /$x/ # non-minimalistic +expected-stderr-pattern: + /1#: unexpected ''/ +expected-exit: e != 0 +--- +name: integer-base-one-3A +description: + some sample code for hexdumping +stdin: + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\x${i#16#1}" + done + print + } | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z5 hv + typeset -i1 wc=0x0A + dasc= + nl=${wc#1#} + while IFS= read -r line; do + line=$line$nl + while [[ -n $line ]]; do + hv=1#${line::1} + if (( (pos & 15) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + print -n "${hv#16#} " + if (( (hv < 32) || (hv > 126) )); then + dasc=$dasc. + else + dasc=$dasc${line::1} + fi + (( (pos++ & 15) == 7 )) && print -n -- '- ' + line=${line:1} + done + done + if (( (pos & 15) != 1 )); then + while (( pos & 15 )); do + print -n ' ' + (( (pos++ & 15) == 7 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 48 65 6C 6C 6F 2C 20 57 - 6F 72 6C 64 21 5C 0A E3 |Hello, World!\..| + 00000010 81 93 E3 82 93 E3 81 AB - E3 81 A1 E3 81 AF EF BC |................| + 00000020 81 0A 01 02 03 04 05 06 - 07 08 09 0A 0B 0C 0D 0E |................| + 00000030 0F 10 11 12 13 14 15 16 - 17 18 19 1A 1B 1C 1D 1E |................| + 00000040 1F 20 21 22 23 24 25 26 - 27 28 29 2A 2B 2C 2D 2E |. !"#$%&'()*+,-.| + 00000050 2F 30 31 32 33 34 35 36 - 37 38 39 3A 3B 3C 3D 3E |/0123456789:;<=>| + 00000060 3F 40 41 42 43 44 45 46 - 47 48 49 4A 4B 4C 4D 4E |?@ABCDEFGHIJKLMN| + 00000070 4F 50 51 52 53 54 55 56 - 57 58 59 5A 5B 5C 5D 5E |OPQRSTUVWXYZ[\]^| + 00000080 5F 60 61 62 63 64 65 66 - 67 68 69 6A 6B 6C 6D 6E |_`abcdefghijklmn| + 00000090 6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E |opqrstuvwxyz{|}~| + 000000A0 7F 80 81 82 83 84 85 86 - 87 88 89 8A 8B 8C 8D 8E |................| + 000000B0 8F 90 91 92 93 94 95 96 - 97 98 99 9A 9B 9C 9D 9E |................| + 000000C0 9F A0 A1 A2 A3 A4 A5 A6 - A7 A8 A9 AA AB AC AD AE |................| + 000000D0 AF B0 B1 B2 B3 B4 B5 B6 - B7 B8 B9 BA BB BC BD BE |................| + 000000E0 BF C0 C1 C2 C3 C4 C5 C6 - C7 C8 C9 CA CB CC CD CE |................| + 000000F0 CF D0 D1 D2 D3 D4 D5 D6 - D7 D8 D9 DA DB DC DD DE |................| + 00000100 DF E0 E1 E2 E3 E4 E5 E6 - E7 E8 E9 EA EB EC ED EE |................| + 00000110 EF F0 F1 F2 F3 F4 F5 F6 - F7 F8 F9 FA FB FC FD FE |................| + 00000120 FF 0A - |..| +--- +name: integer-base-one-3W +description: + some sample code for hexdumping Unicode +stdin: + set -U + { + print 'Hello, World!\\\nこんにちは!' + typeset -Uui16 i=0x100 + # change that to 0xFF once we can handle embedded + # NUL characters in strings / here documents + while (( i++ < 0x1FF )); do + print -n "\u${i#16#1}" + done + print + print \\xff # invalid utf-8 + print \\xc2 # invalid 2-byte + print \\xef\\xbf\\xc0 # invalid 3-byte + print \\xc0\\x80 # non-minimalistic + print \\xe0\\x80\\x80 # non-minimalistic + print '�' # end of range + } | { + typeset -Uui16 -Z11 pos=0 + typeset -Uui16 -Z7 hv + typeset -i1 wc=0x0A + typeset -i lpos + dasc= + nl=${wc#1#} + while IFS= read -r line; do + line=$line$nl + lpos=0 + while (( lpos < ${#line} )); do + wc=1#${line:(lpos++):1} + if (( (wc < 32) || \ + ((wc > 126) && (wc < 160)) )); then + dch=. + elif (( (wc & 0xFF80) == 0xEF80 )); then + dch=� + else + dch=${wc#1#} + fi + if (( (pos & 7) == 7 )); then + dasc=$dasc$dch + dch= + elif (( (pos & 7) == 0 )); then + (( pos )) && print "$dasc|" + print -n "${pos#16#} " + dasc=' |' + fi + let hv=wc + print -n "${hv#16#} " + (( (pos++ & 7) == 3 )) && \ + print -n -- '- ' + dasc=$dasc$dch + done + done + if (( pos & 7 )); then + while (( pos & 7 )); do + print -n ' ' + (( (pos++ & 7) == 3 )) && print -n -- '- ' + done + print "$dasc|" + fi + } +expected-stdout: + 00000000 0048 0065 006C 006C - 006F 002C 0020 0057 |Hello, W| + 00000008 006F 0072 006C 0064 - 0021 005C 000A 3053 |orld!\.こ| + 00000010 3093 306B 3061 306F - FF01 000A 0001 0002 |んにちは!...| + 00000018 0003 0004 0005 0006 - 0007 0008 0009 000A |........| + 00000020 000B 000C 000D 000E - 000F 0010 0011 0012 |........| + 00000028 0013 0014 0015 0016 - 0017 0018 0019 001A |........| + 00000030 001B 001C 001D 001E - 001F 0020 0021 0022 |..... !"| + 00000038 0023 0024 0025 0026 - 0027 0028 0029 002A |#$%&'()*| + 00000040 002B 002C 002D 002E - 002F 0030 0031 0032 |+,-./012| + 00000048 0033 0034 0035 0036 - 0037 0038 0039 003A |3456789:| + 00000050 003B 003C 003D 003E - 003F 0040 0041 0042 |;<=>?@AB| + 00000058 0043 0044 0045 0046 - 0047 0048 0049 004A |CDEFGHIJ| + 00000060 004B 004C 004D 004E - 004F 0050 0051 0052 |KLMNOPQR| + 00000068 0053 0054 0055 0056 - 0057 0058 0059 005A |STUVWXYZ| + 00000070 005B 005C 005D 005E - 005F 0060 0061 0062 |[\]^_`ab| + 00000078 0063 0064 0065 0066 - 0067 0068 0069 006A |cdefghij| + 00000080 006B 006C 006D 006E - 006F 0070 0071 0072 |klmnopqr| + 00000088 0073 0074 0075 0076 - 0077 0078 0079 007A |stuvwxyz| + 00000090 007B 007C 007D 007E - 007F 0080 0081 0082 |{|}~....| + 00000098 0083 0084 0085 0086 - 0087 0088 0089 008A |........| + 000000A0 008B 008C 008D 008E - 008F 0090 0091 0092 |........| + 000000A8 0093 0094 0095 0096 - 0097 0098 0099 009A |........| + 000000B0 009B 009C 009D 009E - 009F 00A0 00A1 00A2 |..... ¡¢| + 000000B8 00A3 00A4 00A5 00A6 - 00A7 00A8 00A9 00AA |£¤¥¦§¨©ª| + 000000C0 00AB 00AC 00AD 00AE - 00AF 00B0 00B1 00B2 |«¬®¯°±²| + 000000C8 00B3 00B4 00B5 00B6 - 00B7 00B8 00B9 00BA |³´µ¶·¸¹º| + 000000D0 00BB 00BC 00BD 00BE - 00BF 00C0 00C1 00C2 |»¼½¾¿ÀÁÂ| + 000000D8 00C3 00C4 00C5 00C6 - 00C7 00C8 00C9 00CA |ÃÄÅÆÇÈÉÊ| + 000000E0 00CB 00CC 00CD 00CE - 00CF 00D0 00D1 00D2 |ËÌÍÎÏÐÑÒ| + 000000E8 00D3 00D4 00D5 00D6 - 00D7 00D8 00D9 00DA |ÓÔÕÖ×ØÙÚ| + 000000F0 00DB 00DC 00DD 00DE - 00DF 00E0 00E1 00E2 |ÛÜÝÞßàáâ| + 000000F8 00E3 00E4 00E5 00E6 - 00E7 00E8 00E9 00EA |ãäåæçèéê| + 00000100 00EB 00EC 00ED 00EE - 00EF 00F0 00F1 00F2 |ëìíîïðñò| + 00000108 00F3 00F4 00F5 00F6 - 00F7 00F8 00F9 00FA |óôõö÷øùú| + 00000110 00FB 00FC 00FD 00FE - 00FF 000A EFFF 000A |ûüýþÿ.�.| + 00000118 EFC2 000A EFEF EFBF - EFC0 000A EFC0 EF80 |�.���.��| + 00000120 000A EFE0 EF80 EF80 - 000A FFFD EFEF EFBF |.���.���| + 00000128 EFBE EFEF EFBF EFBF - 000A |����.| +--- +name: integer-base-one-4 +description: + Check if ksh93-style base-one integers work +category: !smksh +stdin: + set -U + echo 1 $(('a')) + (echo 2f $(('aa'))) 2>&1 | sed "s/^[^']*'/2p '/" + echo 3 $(('…')) + x="'a'" + echo "4 <$x>" + echo 5 $(($x)) + echo 6 $((x)) +expected-stdout: + 1 97 + 2p 'aa': multi-character character constant + 3 8230 + 4 <'a'> + 5 97 + 6 97 +--- +name: ulimit-1 +description: + Check if we can use a specific syntax idiom for ulimit +stdin: + if ! x=$(ulimit -d) || [[ $x = unknown ]]; then + #echo expected to fail on this OS + echo okay + else + ulimit -dS $x && echo okay + fi +expected-stdout: + okay +--- +name: bashiop-1 +description: + Check if GNU bash-like I/O redirection works + Part 1: this is also supported by GNU bash +stdin: + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout &>foo + echo === + cat foo +expected-stdout: + tri + === + ras + dwa +--- +name: bashiop-2a +description: + Check if GNU bash-like I/O redirection works + Part 2: this is *not* supported by GNU bash +stdin: + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout 3&>foo + echo === + cat foo +expected-stdout: + ras + === + dwa + tri +--- +name: bashiop-2b +description: + Check if GNU bash-like I/O redirection works + Part 2: this is *not* supported by GNU bash +stdin: + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout 3>foo &>&3 + echo === + cat foo +expected-stdout: + === + ras + dwa + tri +--- +name: bashiop-2c +description: + Check if GNU bash-like I/O redirection works + Part 2: this is supported by GNU bash 4 only +stdin: + echo mir >foo + set -o noclobber + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout &>>foo + echo === + cat foo +expected-stdout: + tri + === + mir + ras + dwa +--- +name: bashiop-3a +description: + Check if GNU bash-like I/O redirection fails correctly + Part 1: this is also supported by GNU bash +stdin: + echo mir >foo + set -o noclobber + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout &>foo + echo === + cat foo +expected-stdout: + === + mir +expected-stderr-pattern: /.*: cannot (create|overwrite) .*/ +--- +name: bashiop-3b +description: + Check if GNU bash-like I/O redirection fails correctly + Part 2: this is *not* supported by GNU bash +stdin: + echo mir >foo + set -o noclobber + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + threeout &>|foo + echo === + cat foo +expected-stdout: + tri + === + ras + dwa +--- +name: bashiop-4 +description: + Check if GNU bash-like I/O redirection works + Part 4: this is also supported by GNU bash, + but failed in some mksh versions +stdin: + exec 3>&1 + function threeout { + echo ras + echo dwa >&2 + echo tri >&3 + } + function blubb { + [[ -e bar ]] && threeout "$bf" &>foo + } + blubb + echo -n >bar + blubb + echo === + cat foo +expected-stdout: + tri + === + ras + dwa +--- +name: mkshiop-1 +description: + Check for support of more than 9 file descriptors +category: !convfds +stdin: + read -u10 foo 10<<< bar + echo x$foo +expected-stdout: + xbar +--- +name: mkshiop-2 +description: + Check for support of more than 9 file descriptors +category: !convfds +stdin: + exec 12>foo + print -u12 bar + echo baz >&12 + cat foo +expected-stdout: + bar + baz +--- +name: oksh-shcrash +description: + src/regress/bin/ksh/shcrash.sh,v 1.1 +stdin: + deplibs="-lz -lpng /usr/local/lib/libjpeg.la -ltiff -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -ltiff -ljpeg -lz -lpng -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk_pixbuf.la -lz -lpng /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -lz -lz /usr/local/lib/libxml.la -lz -lz -lz /usr/local/lib/libxml.la -lm -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libglib.la /usr/local/lib/libgmodule.la -lintl -lglib -lgmodule /usr/local/lib/libgdk.la /usr/local/lib/libgtk.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade.la -lz -lz -lz /usr/local/lib/libxml.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile /usr/local/lib/libesd.la -lm -lz /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -lglib -lgmodule /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lz /usr/local/lib/libgdk_imlib.la /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lm -lz -lungif -lz -ljpeg -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libglade-gnome.la /usr/local/lib/libglib.la -lm -lm /usr/local/lib/libaudiofile.la -lm -lm -laudiofile -L/usr/local/lib /usr/local/lib/libesd.la -lm -lz -L/usr/local/lib /usr/local/lib/libgnomesupport.la -lm -lz -lm -lglib -L/usr/local/lib /usr/local/lib/libgnome.la -lX11 -lXext /usr/local/lib/libiconv.la -L/usr/local/lib -L/usr/ports/devel/gettext/w-gettext-0.10.40/gettext-0.10.40/intl/.libs /usr/local/lib/libintl.la /usr/local/lib/libgmodule.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgdk.la -lintl -lm -lX11 -lXext -L/usr/X11R6/lib -lglib -lgmodule -L/usr/local/lib /usr/local/lib/libgtk.la -lICE -lSM -lz -lpng /usr/local/lib/libungif.la /usr/local/lib/libjpeg.la -ltiff -lm -lz -lpng /usr/local/lib/libungif.la -lz /usr/local/lib/libjpeg.la -ltiff -L/usr/local/lib -L/usr/X11R6/lib /usr/local/lib/libgdk_imlib.la -lm -L/usr/local/lib /usr/local/lib/libart_lgpl.la -lm -lz -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -lICE -lSM -lm -lX11 -lXext -lintl -lglib -lgmodule -lgdk -lgtk -L/usr/X11R6/lib -lm -lz -lpng -lungif -lz -ljpeg -ltiff -ljpeg -lgdk_imlib -lglib -lm -laudiofile -lm -laudiofile -lesd -L/usr/local/lib /usr/local/lib/libgnomeui.la -L/usr/X11R6/lib -L/usr/local/lib" + specialdeplibs="-lgnomeui -lart_lgpl -lgdk_imlib -ltiff -ljpeg -lungif -lpng -lz -lSM -lICE -lgtk -lgdk -lgmodule -lintl -lXext -lX11 -lgnome -lgnomesupport -lesd -laudiofile -lm -lglib" + for deplib in $deplibs; do + case $deplib in + -L*) + new_libs="$deplib $new_libs" + ;; + *) + case " $specialdeplibs " in + *" $deplib "*) + new_libs="$deplib $new_libs";; + esac + ;; + esac + done +--- +name: oksh-varfunction-mod1 +description: + $OpenBSD: varfunction.sh,v 1.1 2003/12/15 05:28:40 otto Exp $ + Calling + FOO=bar f + where f is a ksh style function, should not set FOO in the current + env. If f is a Bourne style function, FOO should be set. Furthermore, + the function should receive a correct value of FOO. However, differing + from oksh, setting FOO in the function itself must change the value in + setting FOO in the function itself should not change the value in + global environment. + Inspired by PR 2450. +stdin: + function k { + if [ x$FOO != xbar ]; then + echo 1 + return 1 + fi + x=$(env | grep FOO) + if [ "x$x" != "xFOO=bar" ]; then + echo 2 + return 1; + fi + FOO=foo + return 0 + } + b () { + if [ x$FOO != xbar ]; then + echo 3 + return 1 + fi + x=$(env | grep FOO) + if [ "x$x" != "xFOO=bar" ]; then + echo 4 + return 1; + fi + FOO=foo + return 0 + } + FOO=bar k + if [ $? != 0 ]; then + exit 1 + fi + if [ x$FOO != x ]; then + exit 1 + fi + FOO=bar b + if [ $? != 0 ]; then + exit 1 + fi + if [ x$FOO != xfoo ]; then + exit 1 + fi + FOO=barbar + FOO=bar k + if [ $? != 0 ]; then + exit 1 + fi + if [ x$FOO != xbarbar ]; then + exit 1 + fi + FOO=bar b + if [ $? != 0 ]; then + exit 1 + fi + if [ x$FOO != xfoo ]; then + exit 1 + fi +--- +name: fd-cloexec-1 +description: + Verify that file descriptors > 2 are private for Korn shells +file-setup: file 644 "test.sh" + print -u3 Fowl +stdin: + exec 3>&1 + "$__progname" test.sh +expected-exit: e != 0 +expected-stderr: + test.sh[1]: print: -u: 3: bad file descriptor +--- +name: fd-cloexec-2 +description: + Verify that file descriptors > 2 are not private for POSIX shells + See Debian Bug #154540, Closes: #499139 +file-setup: file 644 "test.sh" + print -u3 Fowl +stdin: + test -n "$POSH_VERSION" || set -o sh + exec 3>&1 + "$__progname" test.sh +expected-stdout: + Fowl +--- +name: comsub-1 +description: + COMSUB are currently parsed by hacking lex.c instead of + recursively (see regression-6): matching parenthesēs bug + Fails on: pdksh mksh bash2 bash3 zsh + Passes on: bash4 ksh93 +expected-fail: yes +stdin: + echo $(case 1 in (1) echo yes;; (2) echo no;; esac) + echo $(case 1 in 1) echo yes;; 2) echo no;; esac) +expected-stdout: + yes + yes +--- +name: comsub-2 +description: + RedHat BZ#496791 – another case of missing recursion + in parsing COMSUB expressions + Fails on: pdksh mksh bash2 bash3¹ bash4¹ zsh + Passes on: ksh93 + ① bash[34] seem to choke on comment ending with backslash-newline +expected-fail: yes +stdin: + # a comment with " ' \ + x=$( + echo yes + # a comment with " ' \ + ) + echo $x +expected-stdout: + yes +--- +name: test-stnze-1 +description: + Check that the short form [ $x ] works +stdin: + i=0 + [ -n $x ] + rv=$?; echo $((++i)) $rv + [ $x ] + rv=$?; echo $((++i)) $rv + [ -n "$x" ] + rv=$?; echo $((++i)) $rv + [ "$x" ] + rv=$?; echo $((++i)) $rv + x=0 + [ -n $x ] + rv=$?; echo $((++i)) $rv + [ $x ] + rv=$?; echo $((++i)) $rv + [ -n "$x" ] + rv=$?; echo $((++i)) $rv + [ "$x" ] + rv=$?; echo $((++i)) $rv + x='1 -a 1 = 2' + [ -n $x ] + rv=$?; echo $((++i)) $rv + [ $x ] + rv=$?; echo $((++i)) $rv + [ -n "$x" ] + rv=$?; echo $((++i)) $rv + [ "$x" ] + rv=$?; echo $((++i)) $rv +expected-stdout: + 1 0 + 2 1 + 3 1 + 4 1 + 5 0 + 6 0 + 7 0 + 8 0 + 9 1 + 10 1 + 11 0 + 12 0 +--- +name: test-stnze-2 +description: + Check that the short form [[ $x ]] works (ksh93 extension) +stdin: + i=0 + [[ -n $x ]] + rv=$?; echo $((++i)) $rv + [[ $x ]] + rv=$?; echo $((++i)) $rv + [[ -n "$x" ]] + rv=$?; echo $((++i)) $rv + [[ "$x" ]] + rv=$?; echo $((++i)) $rv + x=0 + [[ -n $x ]] + rv=$?; echo $((++i)) $rv + [[ $x ]] + rv=$?; echo $((++i)) $rv + [[ -n "$x" ]] + rv=$?; echo $((++i)) $rv + [[ "$x" ]] + rv=$?; echo $((++i)) $rv + x='1 -a 1 = 2' + [[ -n $x ]] + rv=$?; echo $((++i)) $rv + [[ $x ]] + rv=$?; echo $((++i)) $rv + [[ -n "$x" ]] + rv=$?; echo $((++i)) $rv + [[ "$x" ]] + rv=$?; echo $((++i)) $rv +expected-stdout: + 1 1 + 2 1 + 3 1 + 4 1 + 5 0 + 6 0 + 7 0 + 8 0 + 9 0 + 10 0 + 11 0 + 12 0 +--- +name: event-subst-1a +description: + Check that '!' substitution in interactive mode works +category: !smksh +file-setup: file 755 "falsetto" + #! /bin/sh + echo molto bene + exit 42 +file-setup: file 755 "!false" + #! /bin/sh + echo si +arguments: !-i! +stdin: + export PATH=.:$PATH + falsetto + echo yeap + !false +expected-exit: 42 +expected-stdout: + molto bene + yeap + molto bene +expected-stderr-pattern: + /.*/ +--- +name: event-subst-1b +description: + Check that '!' substitution in interactive mode works + even when a space separates it from the search command, + which is not what GNU bash provides but required for the + other regression tests below to check +category: !smksh +file-setup: file 755 "falsetto" + #! /bin/sh + echo molto bene + exit 42 +file-setup: file 755 "!" + #! /bin/sh + echo si +arguments: !-i! +stdin: + export PATH=.:$PATH + falsetto + echo yeap + ! false +expected-exit: 42 +expected-stdout: + molto bene + yeap + molto bene +expected-stderr-pattern: + /.*/ +--- +name: event-subst-2 +description: + Check that '!' substitution in interactive mode + does not break things +category: !smksh +file-setup: file 755 "falsetto" + #! /bin/sh + echo molto bene + exit 42 +file-setup: file 755 "!" + #! /bin/sh + echo si +arguments: !-i! +env-setup: !ENV=./Env! +file-setup: file 644 "Env" + PS1=X +stdin: + export PATH=.:$PATH + falsetto + echo yeap + !false + echo meow + ! false + echo = $? + if + ! false; then echo foo; else echo bar; fi +expected-stdout: + molto bene + yeap + molto bene + meow + molto bene + = 42 + foo +expected-stderr-pattern: + /.*/ +--- +name: event-subst-3 +description: + Check that '!' substitution in noninteractive mode is ignored +category: !smksh +file-setup: file 755 "falsetto" + #! /bin/sh + echo molto bene + exit 42 +file-setup: file 755 "!false" + #! /bin/sh + echo si +stdin: + export PATH=.:$PATH + falsetto + echo yeap + !false + echo meow + ! false + echo = $? + if + ! false; then echo foo; else echo bar; fi +expected-stdout: + molto bene + yeap + si + meow + = 0 + foo +--- +name: nounset-1 +description: + Check that "set -u" matches (future) SUSv4 requirement +stdin: + (set -u + try() { + local v + eval v=\$$1 + if [[ -n $v ]]; then + echo $1=nz + else + echo $1=zf + fi + } + x=y + (echo $x) + echo =1 + (echo $y) + echo =2 + (try x) + echo =3 + (try y) + echo =4 + (try 0) + echo =5 + (try 2) + echo =6 + (try) + echo =7 + (echo at=$@) + echo =8 + (echo asterisk=$*) + echo =9 + (echo $?) + echo =10 + (echo $!) + echo =11 + (echo $-) + echo =12 + #(echo $_) + #echo =13 + (echo $#) + echo =14 + (mypid=$$; try mypid) + echo =15 + ) 2>&1 | sed -e 's/^[^]]*]//' -e 's/^[^:]*: *//' +expected-stdout: + y + =1 + y: parameter not set + =2 + x=nz + =3 + y: parameter not set + =4 + 0=nz + =5 + 2: parameter not set + =6 + 1: parameter not set + =7 + at= + =8 + asterisk= + =9 + 0 + =10 + !: parameter not set + =11 + ush + =12 + 0 + =14 + mypid=nz + =15 +--- +name: nameref-1 +description: + Testsuite for nameref (bound variables) +stdin: + bar=global + typeset -n ir2=bar + typeset -n ind=ir2 + echo !ind: ${!ind} + echo ind: $ind + echo !ir2: ${!ir2} + echo ir2: $ir2 + typeset +n ind + echo !ind: ${!ind} + echo ind: $ind + typeset -n ir2=ind + echo !ir2: ${!ir2} + echo ir2: $ir2 + set|grep ^ir2|sed 's/^/s1: /' + typeset|grep ' ir2'|sed -e 's/^/s2: /' -e 's/nameref/typeset -n/' + set -A blub -- e1 e2 e3 + typeset -n ind=blub + typeset -n ir2=blub[2] + echo !ind[1]: ${!ind[1]} + echo !ir2: $!ir2 + echo ind[1]: ${ind[1]} + echo ir2: $ir2 +expected-stdout: + !ind: bar + ind: global + !ir2: bar + ir2: global + !ind: ind + ind: ir2 + !ir2: ind + ir2: ir2 + s1: ir2=ind + s2: typeset -n ir2 + !ind[1]: 1 + !ir2: ir2 + ind[1]: e2 + ir2: e3 +--- +name: nameref-2da +description: + Testsuite for nameref (bound variables) + Functions, argument given directly, after local +stdin: + function foo { + typeset bar=lokal baz=auch + typeset -n v=bar + echo entering + echo !v: ${!v} + echo !bar: ${!bar} + echo !baz: ${!baz} + echo bar: $bar + echo v: $v + v=123 + echo bar: $bar + echo v: $v + echo exiting + } + bar=global + echo bar: $bar + foo bar + echo bar: $bar +expected-stdout: + bar: global + entering + !v: bar + !bar: bar + !baz: baz + bar: lokal + v: lokal + bar: 123 + v: 123 + exiting + bar: global +--- +name: nameref-3 +description: + Advanced testsuite for bound variables (ksh93 fails this) +stdin: + typeset -n foo=bar[i] + set -A bar -- b c a + for i in 0 1 2 3; do + print $i $foo . + done +expected-stdout: + 0 b . + 1 c . + 2 a . + 3 . +--- +name: better-parens-1a +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + if ( (echo fubar) | tr u x); then + echo ja + else + echo nein + fi +expected-stdout: + fxbar + ja +--- +name: better-parens-1b +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + echo $( (echo fubar) | tr u x) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-2a +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + if ((echo fubar) | tr u x); then + echo ja + else + echo nein + fi +expected-stdout: + fxbar + ja +--- +name: better-parens-2b +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + echo $((echo fubar) | tr u x) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-3a +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + if ( (echo fubar) | (tr u x)); then + echo ja + else + echo nein + fi +expected-stdout: + fxbar + ja +--- +name: better-parens-3b +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + echo $( (echo fubar) | (tr u x)) $? +expected-stdout: + fxbar 0 +--- +name: better-parens-4a +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + if ((echo fubar) | (tr u x)); then + echo ja + else + echo nein + fi +expected-stdout: + fxbar + ja +--- +name: better-parens-4b +description: + Check support for ((…)) and $((…)) vs (…) and $(…) +stdin: + echo $((echo fubar) | (tr u x)) $? +expected-stdout: + fxbar 0 +--- +name: echo-test-1 +description: + Test what the echo builtin does (mksh) +stdin: + echo -n 'foo\x40bar' + echo -e '\tbaz' +expected-stdout: + foo@bar baz +--- +name: echo-test-2 +description: + Test what the echo builtin does (POSIX) + Note: this follows Debian Policy 10.4 which mandates + that -n shall be treated as an option, not XSI which + mandates it shall be treated as string but escapes + shall be expanded. +stdin: + test -n "$POSH_VERSION" || set -o sh + echo -n 'foo\x40bar' + echo -e '\tbaz' +expected-stdout: + foo\x40bar-e \tbaz +--- +name: utilities-getopts-1 +description: + getopts sets OPTIND correctly for unparsed option +stdin: + set -- -a -a -x + while getopts :a optc; do + echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc." + done + echo done +expected-stdout: + OPTARG=, OPTIND=2, optc=a. + OPTARG=, OPTIND=3, optc=a. + OPTARG=x, OPTIND=4, optc=?. + done +--- +name: utilities-getopts-2 +description: + Check OPTARG +stdin: + set -- -a Mary -x + while getopts a: optc; do + echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc." + done + echo done +expected-stdout: + OPTARG=Mary, OPTIND=3, optc=a. + OPTARG=, OPTIND=4, optc=?. + done +expected-stderr-pattern: /.*-x.*option/ +--- +name: wcswidth-1 +description: + Check the new wcswidth feature +stdin: + s=何 + set +U + print octets: ${#s} . + print 8-bit width: ${%s} . + set -U + print characters: ${#s} . + print columns: ${%s} . + s=� + set +U + print octets: ${#s} . + print 8-bit width: ${%s} . + set -U + print characters: ${#s} . + print columns: ${%s} . +expected-stdout: + octets: 3 . + 8-bit width: -1 . + characters: 1 . + columns: 2 . + octets: 3 . + 8-bit width: 3 . + characters: 1 . + columns: 1 . +--- +name: wcswidth-2 +description: + Check some corner cases +stdin: + print % $% . + set -U + x='a b' + print c ${%x} . + set +U + x='a b' + print d ${%x} . +expected-stdout: + % $% . + c -1 . + d -1 . +--- +name: wcswidth-3 +description: + Check some corner cases +stdin: + print ${%} . +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: wcswidth-4a +description: + Check some corner cases +stdin: + print ${%*} . +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: wcswidth-4b +description: + Check some corner cases +stdin: + print ${%@} . +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- +name: wcswidth-4c +description: + Check some corner cases +stdin: + : + print ${%?} . +expected-stdout: + 1 . +--- +name: realpath-1 +description: + Check proper return values for realpath +category: os:mirbsd +stdin: + wd=$(realpath .) + mkdir dir + :>file + :>dir/file + ln -s dir lndir + ln -s file lnfile + ln -s nix lnnix + ln -s . lnself + i=0 + chk() { + typeset x y + x=$(realpath "$wd/$1" 2>&1); y=$? + print $((++i)) "?$1" =${x##*$wd/} !$y + } + chk dir + chk dir/ + chk dir/file + chk dir/nix + chk file + chk file/ + chk file/file + chk file/nix + chk nix + chk nix/ + chk nix/file + chk nix/nix + chk lndir + chk lndir/ + chk lndir/file + chk lndir/nix + chk lnfile + chk lnfile/ + chk lnfile/file + chk lnfile/nix + chk lnnix + chk lnnix/ + chk lnnix/file + chk lnnix/nix + chk lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself + rm lnself +expected-stdout: + 1 ?dir =dir !0 + 2 ?dir/ =dir !0 + 3 ?dir/file =dir/file !0 + 4 ?dir/nix =dir/nix !0 + 5 ?file =file !0 + 6 ?file/ =file/: Not a directory !20 + 7 ?file/file =file/file: Not a directory !20 + 8 ?file/nix =file/nix: Not a directory !20 + 9 ?nix =nix !0 + 10 ?nix/ =nix !0 + 11 ?nix/file =nix/file: No such file or directory !2 + 12 ?nix/nix =nix/nix: No such file or directory !2 + 13 ?lndir =dir !0 + 14 ?lndir/ =dir !0 + 15 ?lndir/file =dir/file !0 + 16 ?lndir/nix =dir/nix !0 + 17 ?lnfile =file !0 + 18 ?lnfile/ =lnfile/: Not a directory !20 + 19 ?lnfile/file =lnfile/file: Not a directory !20 + 20 ?lnfile/nix =lnfile/nix: Not a directory !20 + 21 ?lnnix =nix !0 + 22 ?lnnix/ =nix !0 + 23 ?lnnix/file =lnnix/file: No such file or directory !2 + 24 ?lnnix/nix =lnnix/nix: No such file or directory !2 + 25 ?lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself =lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself/lnself: Too many levels of symbolic links !62 +--- diff --git a/mksh/src/edit.c b/mksh/src/edit.c new file mode 100644 index 0000000..905de7e --- /dev/null +++ b/mksh/src/edit.c @@ -0,0 +1,5249 @@ +/* $OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $ */ +/* $OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $ */ +/* $OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $ */ +/* $OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $"); + +/* + * in later versions we might use libtermcap for this, but since external + * dependencies are problematic, this has not yet been decided on; another + * good string is "\033c" except on hardware terminals like the DEC VT420 + * which do a full power cycle then... + */ +#ifndef MKSH_CLS_STRING +#define MKSH_CLS_STRING "\033[;H\033[J" +#endif +#ifndef MKSH_CLRTOEOL_STRING +#define MKSH_CLRTOEOL_STRING "\033[K" +#endif + +/* tty driver characters we are interested in */ +typedef struct { + int erase; + int kill; + int werase; + int intr; + int quit; + int eof; +} X_chars; + +static X_chars edchars; + +/* x_fc_glob() flags */ +#define XCF_COMMAND BIT(0) /* Do command completion */ +#define XCF_FILE BIT(1) /* Do file completion */ +#define XCF_FULLPATH BIT(2) /* command completion: store full path */ +#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE) + +static char editmode; +static int xx_cols; /* for Emacs mode */ +static int modified; /* buffer has been "modified" */ +static char holdbuf[LINE]; /* place to hold last edit buffer */ + +static int x_getc(void); +static void x_putcf(int); +static bool x_mode(bool); +static int x_do_comment(char *, int, int *); +static void x_print_expansions(int, char *const *, bool); +static int x_cf_glob(int, const char *, int, int, int *, int *, char ***, + bool *); +static int x_longest_prefix(int, char *const *); +static int x_basename(const char *, const char *); +static void x_free_words(int, char **); +static int x_escape(const char *, size_t, int (*)(const char *, size_t)); +static int x_emacs(char *, size_t); +static void x_init_emacs(void); +static void x_init_prompt(void); +#if !MKSH_S_NOVI +static int x_vi(char *, size_t); +#endif + +#define x_flush() shf_flush(shl_out) +#ifdef MKSH_SMALL +#define x_putc(c) x_putcf(c) +#else +#define x_putc(c) shf_putc((c), shl_out) +#endif + +static int path_order_cmp(const void *aa, const void *bb); +static char *add_glob(const char *, int) + MKSH_A_NONNULL((nonnull (1))) + MKSH_A_BOUNDED(string, 1, 2); +static void glob_table(const char *, XPtrV *, struct table *); +static void glob_path(int flags, const char *, XPtrV *, const char *); +static int x_file_glob(int, const char *, int, char ***) + MKSH_A_NONNULL((nonnull (2))) + MKSH_A_BOUNDED(string, 2, 3); +static int x_command_glob(int, const char *, int, char ***) + MKSH_A_NONNULL((nonnull (2))) + MKSH_A_BOUNDED(string, 2, 3); +static int x_locate_word(const char *, int, int, int *, bool *); + +static int x_e_getmbc(char *); +static int x_e_rebuildline(const char *); + +/* +++ generic editing functions +++ */ + +/* Called from main */ +void +x_init(void) +{ + /* set to -2 to force initial binding */ + edchars.erase = edchars.kill = edchars.intr = edchars.quit = + edchars.eof = -2; + /* default value for deficient systems */ + edchars.werase = 027; /* ^W */ + x_init_emacs(); +} + +/* + * read an edited command line + */ +int +x_read(char *buf, size_t len) +{ + int i; + + x_mode(true); + modified = 1; + if (Flag(FEMACS) || Flag(FGMACS)) + i = x_emacs(buf, len); +#if !MKSH_S_NOVI + else if (Flag(FVI)) + i = x_vi(buf, len); +#endif + else + i = -1; /* internal error */ + editmode = 0; + x_mode(false); + return (i); +} + +/* tty I/O */ + +static int +x_getc(void) +{ + char c; + int n; + + while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) + if (trap) { + x_mode(false); + runtraps(0); +#ifdef SIGWINCH + if (got_winch) { + change_winsz(); + if (x_cols != xx_cols && editmode == 1) { + /* redraw line in Emacs mode */ + xx_cols = x_cols; + x_e_rebuildline(MKSH_CLRTOEOL_STRING); + } + } +#endif + x_mode(true); + } + return ((n == 1) ? (int)(unsigned char)c : -1); +} + +static void +x_putcf(int c) +{ + shf_putc(c, shl_out); +} + +/********************************* + * Misc common code for vi/emacs * + *********************************/ + +/* Handle the commenting/uncommenting of a line. + * Returns: + * 1 if a carriage return is indicated (comment added) + * 0 if no return (comment removed) + * -1 if there is an error (not enough room for comment chars) + * If successful, *lenp contains the new length. Note: cursor should be + * moved to the start of the line after (un)commenting. + */ +static int +x_do_comment(char *buf, int bsize, int *lenp) +{ + int i, j, len = *lenp; + + if (len == 0) + return (1); /* somewhat arbitrary - it's what AT&T ksh does */ + + /* Already commented? */ + if (buf[0] == '#') { + bool saw_nl = false; + + for (j = 0, i = 1; i < len; i++) { + if (!saw_nl || buf[i] != '#') + buf[j++] = buf[i]; + saw_nl = buf[i] == '\n'; + } + *lenp = j; + return (0); + } else { + int n = 1; + + /* See if there's room for the #s - 1 per \n */ + for (i = 0; i < len; i++) + if (buf[i] == '\n') + n++; + if (len + n >= bsize) + return (-1); + /* Now add them... */ + for (i = len, j = len + n; --i >= 0; ) { + if (buf[i] == '\n') + buf[--j] = '#'; + buf[--j] = buf[i]; + } + buf[0] = '#'; + *lenp += n; + return (1); + } +} + +/**************************************************** + * Common file/command completion code for vi/emacs * + ****************************************************/ + +static void +x_print_expansions(int nwords, char * const *words, bool is_command) +{ + bool use_copy = false; + int prefix_len; + XPtrV l = { NULL, NULL, NULL }; + + /* Check if all matches are in the same directory (in this + * case, we want to omit the directory name) + */ + if (!is_command && + (prefix_len = x_longest_prefix(nwords, words)) > 0) { + int i; + + /* Special case for 1 match (prefix is whole word) */ + if (nwords == 1) + prefix_len = x_basename(words[0], NULL); + /* Any (non-trailing) slashes in non-common word suffixes? */ + for (i = 0; i < nwords; i++) + if (x_basename(words[i] + prefix_len, NULL) > + prefix_len) + break; + /* All in same directory? */ + if (i == nwords) { + while (prefix_len > 0 && words[0][prefix_len - 1] != '/') + prefix_len--; + use_copy = true; + XPinit(l, nwords + 1); + for (i = 0; i < nwords; i++) + XPput(l, words[i] + prefix_len); + XPput(l, NULL); + } + } + /* + * Enumerate expansions + */ + x_putc('\r'); + x_putc('\n'); + pr_list(use_copy ? (char **)XPptrv(l) : words); + + if (use_copy) + XPfree(l); /* not x_free_words() */ +} + +/** + * Do file globbing: + * - appends * to (copy of) str if no globbing chars found + * - does expansion, checks for no match, etc. + * - sets *wordsp to array of matching strings + * - returns number of matching strings + */ +static int +x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) +{ + char *toglob, **words; + int nwords, i, idx; + bool escaping; + XPtrV w; + struct source *s, *sold; + + if (slen < 0) + return (0); + + toglob = add_glob(str, slen); + + /* remove all escaping backward slashes */ + escaping = false; + for (i = 0, idx = 0; toglob[i]; i++) { + if (toglob[i] == '\\' && !escaping) { + escaping = true; + continue; + } + /* specially escape escaped [ or $ or ` for globbing */ + if (escaping && (toglob[i] == '[' || + toglob[i] == '$' || toglob[i] == '`')) + toglob[idx++] = QCHAR; + + toglob[idx] = toglob[i]; + idx++; + if (escaping) + escaping = false; + } + toglob[idx] = '\0'; + + /* + * Convert "foo*" (toglob) to an array of strings (words) + */ + sold = source; + s = pushs(SWSTR, ATEMP); + s->start = s->str = toglob; + source = s; + if (yylex(ONEWORD | LQCHAR) != LWORD) { + source = sold; + internal_warningf("fileglob: substitute error"); + return (0); + } + source = sold; + XPinit(w, 32); + expand(yylval.cp, &w, DOGLOB | DOTILDE | DOMARKDIRS); + XPput(w, NULL); + words = (char **)XPclose(w); + + for (nwords = 0; words[nwords]; nwords++) + ; + if (nwords == 1) { + struct stat statb; + + /* Check if globbing failed (returned glob pattern), + * but be careful (E.g. toglob == "ab*" when the file + * "ab*" exists is not an error). + * Also, check for empty result - happens if we tried + * to glob something which evaluated to an empty + * string (e.g., "$FOO" when there is no FOO, etc). + */ + if ((strcmp(words[0], toglob) == 0 && + stat(words[0], &statb) < 0) || + words[0][0] == '\0') { + x_free_words(nwords, words); + words = NULL; + nwords = 0; + } + } + afree(toglob, ATEMP); + + if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL) + x_free_words(nwords, words); + + return (nwords); +} + +/* Data structure used in x_command_glob() */ +struct path_order_info { + char *word; + int base; + int path_order; +}; + +/* Compare routine used in x_command_glob() */ +static int +path_order_cmp(const void *aa, const void *bb) +{ + const struct path_order_info *a = (const struct path_order_info *)aa; + const struct path_order_info *b = (const struct path_order_info *)bb; + int t; + + t = strcmp(a->word + a->base, b->word + b->base); + return (t ? t : a->path_order - b->path_order); +} + +static int +x_command_glob(int flags, const char *str, int slen, char ***wordsp) +{ + char *toglob, *pat, *fpath; + int nwords; + XPtrV w; + struct block *l; + + if (slen < 0) + return (0); + + toglob = add_glob(str, slen); + + /* Convert "foo*" (toglob) to a pattern for future use */ + pat = evalstr(toglob, DOPAT | DOTILDE); + afree(toglob, ATEMP); + + XPinit(w, 32); + + glob_table(pat, &w, &keywords); + glob_table(pat, &w, &aliases); + glob_table(pat, &w, &builtins); + for (l = e->loc; l; l = l->next) + glob_table(pat, &w, &l->funs); + + glob_path(flags, pat, &w, path); + if ((fpath = str_val(global("FPATH"))) != null) + glob_path(flags, pat, &w, fpath); + + nwords = XPsize(w); + + if (!nwords) { + *wordsp = NULL; + XPfree(w); + return (0); + } + /* Sort entries */ + if (flags & XCF_FULLPATH) { + /* Sort by basename, then path order */ + struct path_order_info *info, *last_info = NULL; + char **words = (char **)XPptrv(w); + int i, path_order = 0; + + info = (struct path_order_info *) + alloc(nwords * sizeof(struct path_order_info), ATEMP); + for (i = 0; i < nwords; i++) { + info[i].word = words[i]; + info[i].base = x_basename(words[i], NULL); + if (!last_info || info[i].base != last_info->base || + strncmp(words[i], last_info->word, info[i].base) != 0) { + last_info = &info[i]; + path_order++; + } + info[i].path_order = path_order; + } + qsort(info, nwords, sizeof(struct path_order_info), + path_order_cmp); + for (i = 0; i < nwords; i++) + words[i] = info[i].word; + afree(info, ATEMP); + } else { + /* Sort and remove duplicate entries */ + char **words = (char **)XPptrv(w); + int i, j; + + qsort(words, nwords, sizeof(void *), xstrcmp); + for (i = j = 0; i < nwords - 1; i++) { + if (strcmp(words[i], words[i + 1])) + words[j++] = words[i]; + else + afree(words[i], ATEMP); + } + words[j++] = words[i]; + nwords = j; + w.cur = (void **)&words[j]; + } + + XPput(w, NULL); + *wordsp = (char **)XPclose(w); + + return (nwords); +} + +#define IS_WORDC(c) (!ctype(c, C_LEX1) && (c) != '\'' && (c) != '"' && \ + (c) != '`' && (c) != '=' && (c) != ':') + +static int +x_locate_word(const char *buf, int buflen, int pos, int *startp, + bool *is_commandp) +{ + int start, end; + + /* Bad call? Probably should report error */ + if (pos < 0 || pos > buflen) { + *startp = pos; + *is_commandp = false; + return (0); + } + /* The case where pos == buflen happens to take care of itself... */ + + start = pos; + /* Keep going backwards to start of word (has effect of allowing + * one blank after the end of a word) + */ + for (; (start > 0 && IS_WORDC(buf[start - 1])) || + (start > 1 && buf[start - 2] == '\\'); start--) + ; + /* Go forwards to end of word */ + for (end = start; end < buflen && IS_WORDC(buf[end]); end++) { + if (buf[end] == '\\' && (end + 1) < buflen) + end++; + } + + if (is_commandp) { + bool iscmd; + int p = start - 1; + + /* Figure out if this is a command */ + while (p >= 0 && ksh_isspace(buf[p])) + p--; + iscmd = p < 0 || vstrchr(";|&()`", buf[p]); + if (iscmd) { + /* If command has a /, path, etc. is not searched; + * only current directory is searched which is just + * like file globbing. + */ + for (p = start; p < end; p++) + if (buf[p] == '/') + break; + iscmd = p == end; + } + *is_commandp = iscmd; + } + *startp = start; + + return (end - start); +} + +static int +x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, + int *endp, char ***wordsp, bool *is_commandp) +{ + int len, nwords; + char **words = NULL; + bool is_command; + + len = x_locate_word(buf, buflen, pos, startp, &is_command); + if (!(flags & XCF_COMMAND)) + is_command = false; + /* Don't do command globing on zero length strings - it takes too + * long and isn't very useful. File globs are more likely to be + * useful, so allow these. + */ + if (len == 0 && is_command) + return (0); + + nwords = is_command ? + x_command_glob(flags, buf + *startp, len, &words) : + x_file_glob(flags, buf + *startp, len, &words); + if (nwords == 0) { + *wordsp = NULL; + return (0); + } + if (is_commandp) + *is_commandp = is_command; + *wordsp = words; + *endp = *startp + len; + + return (nwords); +} + +/* Given a string, copy it and possibly add a '*' to the end. + * The new string is returned. + */ +static char * +add_glob(const char *str, int slen) +{ + char *toglob, *s; + bool saw_slash = false; + + if (slen < 0) + return (NULL); + + /* for clang's static analyser, the nonnull attribute isn't enough */ + mkssert(str != NULL); + + strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */ + toglob[slen] = '\0'; + + /* + * If the pathname contains a wildcard (an unquoted '*', + * '?', or '[') or parameter expansion ('$'), or a ~username + * with no trailing slash, then it is globbed based on that + * value (i.e., without the appended '*'). + */ + for (s = toglob; *s; s++) { + if (*s == '\\' && s[1]) + s++; + else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' || + (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */ + (*s == '+' || *s == '@' || *s == '!'))) + break; + else if (*s == '/') + saw_slash = true; + } + if (!*s && (*toglob != '~' || saw_slash)) { + toglob[slen] = '*'; + toglob[slen + 1] = '\0'; + } + return (toglob); +} + +/* + * Find longest common prefix + */ +static int +x_longest_prefix(int nwords, char * const * words) +{ + int i, j, prefix_len; + char *p; + + if (nwords <= 0) + return (0); + + prefix_len = strlen(words[0]); + for (i = 1; i < nwords; i++) + for (j = 0, p = words[i]; j < prefix_len; j++) + if (p[j] != words[0][j]) { + prefix_len = j; + break; + } + return (prefix_len); +} + +static void +x_free_words(int nwords, char **words) +{ + while (nwords) + afree(words[--nwords], ATEMP); + afree(words, ATEMP); +} + +/* Return the offset of the basename of string s (which ends at se - need not + * be null terminated). Trailing slashes are ignored. If s is just a slash, + * then the offset is 0 (actually, length - 1). + * s Return + * /etc 1 + * /etc/ 1 + * /etc// 1 + * /etc/fo 5 + * foo 0 + * /// 2 + * 0 + */ +static int +x_basename(const char *s, const char *se) +{ + const char *p; + + if (se == NULL) + se = s + strlen(s); + if (s == se) + return (0); + + /* Skip trailing slashes */ + for (p = se - 1; p > s && *p == '/'; p--) + ; + for (; p > s && *p != '/'; p--) + ; + if (*p == '/' && p + 1 < se) + p++; + + return (p - s); +} + +/* + * Apply pattern matching to a table: all table entries that match a pattern + * are added to wp. + */ +static void +glob_table(const char *pat, XPtrV *wp, struct table *tp) +{ + struct tstate ts; + struct tbl *te; + + ktwalk(&ts, tp); + while ((te = ktnext(&ts))) + if (gmatchx(te->name, pat, false)) { + char *cp; + + strdupx(cp, te->name, ATEMP); + XPput(*wp, cp); + } +} + +static void +glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) +{ + const char *sp, *p; + char *xp, **words; + int staterr, pathlen, patlen, oldsize, newsize, i, j; + XString xs; + + patlen = strlen(pat) + 1; + sp = lpath; + Xinit(xs, xp, patlen + 128, ATEMP); + while (sp) { + xp = Xstring(xs, xp); + if (!(p = cstrchr(sp, ':'))) + p = sp + strlen(sp); + pathlen = p - sp; + if (pathlen) { + /* Copy sp into xp, stuffing any MAGIC characters + * on the way + */ + const char *s = sp; + + XcheckN(xs, xp, pathlen * 2); + while (s < p) { + if (ISMAGIC(*s)) + *xp++ = MAGIC; + *xp++ = *s++; + } + *xp++ = '/'; + pathlen++; + } + sp = p; + XcheckN(xs, xp, patlen); + memcpy(xp, pat, patlen); + + oldsize = XPsize(*wp); + glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ + newsize = XPsize(*wp); + + /* Check that each match is executable... */ + words = (char **)XPptrv(*wp); + for (i = j = oldsize; i < newsize; i++) { + staterr = 0; + if ((search_access(words[i], X_OK, &staterr) >= 0) || + (staterr == EISDIR)) { + words[j] = words[i]; + if (!(flags & XCF_FULLPATH)) + memmove(words[j], words[j] + pathlen, + strlen(words[j] + pathlen) + 1); + j++; + } else + afree(words[i], ATEMP); + } + wp->cur = (void **)&words[j]; + + if (!*sp++) + break; + } + Xfree(xs, xp); +} + +/* + * if argument string contains any special characters, they will + * be escaped and the result will be put into edit buffer by + * keybinding-specific function + */ +static int +x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t)) +{ + size_t add = 0, wlen = len; + const char *ifs = str_val(local("IFS", 0)); + int rval = 0; + + while (wlen - add > 0) + if (vstrchr("\"#$&'()*:;<=>?[\\`{|}", s[add]) || + vstrchr(ifs, s[add])) { + if (putbuf_func(s, add) != 0) { + rval = -1; + break; + } + putbuf_func(s[add] == '\n' ? "'" : "\\", 1); + putbuf_func(&s[add], 1); + if (s[add] == '\n') + putbuf_func("'", 1); + + add++; + wlen -= add; + s += add; + add = 0; + } else + ++add; + if (wlen > 0 && rval == 0) + rval = putbuf_func(s, wlen); + + return (rval); +} + + +/* +++ emacs editing mode +++ */ + +static Area aedit; +#define AEDIT &aedit /* area for kill ring and macro defns */ + +/* values returned by keyboard functions */ +#define KSTD 0 +#define KEOL 1 /* ^M, ^J */ +#define KINTR 2 /* ^G, ^C */ + +struct x_ftab { + int (*xf_func)(int c); + const char *xf_name; + short xf_flags; +}; + +struct x_defbindings { + unsigned char xdb_func; /* XFUNC_* */ + unsigned char xdb_tab; + unsigned char xdb_char; +}; + +#define XF_ARG 1 /* command takes number prefix */ +#define XF_NOBIND 2 /* not allowed to bind to function */ +#define XF_PREFIX 4 /* function sets prefix */ + +/* Separator for completion */ +#define is_cfs(c) ((c) == ' ' || (c) == '\t' || (c) == '"' || (c) == '\'') +/* Separator for motion */ +#define is_mfs(c) (!(ksh_isalnux(c) || (c) == '$' || ((c) & 0x80))) + +#define X_NTABS 3 /* normal, meta1, meta2 */ +#define X_TABSZ 256 /* size of keydef tables etc */ + +/* Arguments for do_complete() + * 0 = enumerate M-= complete as much as possible and then list + * 1 = complete M-Esc + * 2 = list M-? + */ +typedef enum { + CT_LIST, /* list the possible completions */ + CT_COMPLETE, /* complete to longest prefix */ + CT_COMPLIST /* complete and then list (if non-exact) */ +} Comp_type; + +/* + * The following are used for my horizontal scrolling stuff + */ +static char *xbuf; /* beg input buffer */ +static char *xend; /* end input buffer */ +static char *xcp; /* current position */ +static char *xep; /* current end */ +static char *xbp; /* start of visible portion of input buffer */ +static char *xlp; /* last char visible on screen */ +static int x_adj_ok; +/* + * we use x_adj_done so that functions can tell + * whether x_adjust() has been called while they are active. + */ +static int x_adj_done; + +static int x_col; +static int x_displen; +static int x_arg; /* general purpose arg */ +static int x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */ + +static int xlp_valid; + +static char **x_histp; /* history position */ +static int x_nextcmd; /* for newline-and-next */ +static char *xmp; /* mark pointer */ +static unsigned char x_last_command; +static unsigned char (*x_tab)[X_TABSZ]; /* key definition */ +#ifndef MKSH_SMALL +static char *(*x_atab)[X_TABSZ]; /* macro definitions */ +#endif +static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8]; +#define KILLSIZE 20 +static char *killstack[KILLSIZE]; +static int killsp, killtp; +static int x_curprefix; +#ifndef MKSH_SMALL +static char *macroptr = NULL; /* bind key macro active? */ +#endif +#if !MKSH_S_NOVI +static int cur_col; /* current column on line */ +static int pwidth; /* width of prompt */ +static int prompt_trunc; /* how much of prompt to truncate */ +static int winwidth; /* width of window */ +static char *wbuf[2]; /* window buffers */ +static int wbuf_len; /* length of window buffers (x_cols - 3) */ +static int win; /* window buffer in use */ +static char morec; /* more character at right of window */ +static int lastref; /* argument to last refresh() */ +static int holdlen; /* length of holdbuf */ +#endif +static int prompt_redraw; /* 0 if newline forced after prompt */ + +static int x_ins(const char *); +static void x_delete(int, int); +static int x_bword(void); +static int x_fword(int); +static void x_goto(char *); +static void x_bs3(char **); +static int x_size_str(char *); +static int x_size2(char *, char **); +static void x_zots(char *); +static void x_zotc2(int); +static void x_zotc3(char **); +static void x_load_hist(char **); +static int x_search(char *, int, int); +#ifndef MKSH_SMALL +static int x_search_dir(int); +#endif +static int x_match(char *, char *); +static void x_redraw(int); +static void x_push(int); +static char *x_mapin(const char *, Area *) + MKSH_A_NONNULL((nonnull (1))); +static char *x_mapout(int); +static void x_mapout2(int, char **); +static void x_print(int, int); +static void x_adjust(void); +static void x_e_ungetc(int); +static int x_e_getc(void); +static void x_e_putc2(int); +static void x_e_putc3(const char **); +static void x_e_puts(const char *); +#ifndef MKSH_SMALL +static int x_fold_case(int); +#endif +static char *x_lastcp(void); +static void do_complete(int, Comp_type); + +static int unget_char = -1; + +static int x_do_ins(const char *, size_t); +static void bind_if_not_bound(int, int, int); + +enum emacs_funcs { +#define EMACSFN_ENUMS +#include "emacsfn.h" + XFUNC_MAX +}; + +#define EMACSFN_DEFNS +#include "emacsfn.h" + +static const struct x_ftab x_ftab[] = { +#define EMACSFN_ITEMS +#include "emacsfn.h" + { 0, NULL, 0 } +}; + +static struct x_defbindings const x_defbindings[] = { + { XFUNC_del_back, 0, CTRL('?') }, + { XFUNC_del_bword, 1, CTRL('?') }, + { XFUNC_eot_del, 0, CTRL('D') }, + { XFUNC_del_back, 0, CTRL('H') }, + { XFUNC_del_bword, 1, CTRL('H') }, + { XFUNC_del_bword, 1, 'h' }, + { XFUNC_mv_bword, 1, 'b' }, + { XFUNC_mv_fword, 1, 'f' }, + { XFUNC_del_fword, 1, 'd' }, + { XFUNC_mv_back, 0, CTRL('B') }, + { XFUNC_mv_forw, 0, CTRL('F') }, + { XFUNC_search_char_forw, 0, CTRL(']') }, + { XFUNC_search_char_back, 1, CTRL(']') }, + { XFUNC_newline, 0, CTRL('M') }, + { XFUNC_newline, 0, CTRL('J') }, + { XFUNC_end_of_text, 0, CTRL('_') }, + { XFUNC_abort, 0, CTRL('G') }, + { XFUNC_prev_com, 0, CTRL('P') }, + { XFUNC_next_com, 0, CTRL('N') }, + { XFUNC_nl_next_com, 0, CTRL('O') }, + { XFUNC_search_hist, 0, CTRL('R') }, + { XFUNC_beg_hist, 1, '<' }, + { XFUNC_end_hist, 1, '>' }, + { XFUNC_goto_hist, 1, 'g' }, + { XFUNC_mv_end, 0, CTRL('E') }, + { XFUNC_mv_begin, 0, CTRL('A') }, + { XFUNC_draw_line, 0, CTRL('L') }, + { XFUNC_cls, 1, CTRL('L') }, + { XFUNC_meta1, 0, CTRL('[') }, + { XFUNC_meta2, 0, CTRL('X') }, + { XFUNC_kill, 0, CTRL('K') }, + { XFUNC_yank, 0, CTRL('Y') }, + { XFUNC_meta_yank, 1, 'y' }, + { XFUNC_literal, 0, CTRL('^') }, + { XFUNC_comment, 1, '#' }, + { XFUNC_transpose, 0, CTRL('T') }, + { XFUNC_complete, 1, CTRL('[') }, + { XFUNC_comp_list, 0, CTRL('I') }, + { XFUNC_comp_list, 1, '=' }, + { XFUNC_enumerate, 1, '?' }, + { XFUNC_expand, 1, '*' }, + { XFUNC_comp_file, 1, CTRL('X') }, + { XFUNC_comp_comm, 2, CTRL('[') }, + { XFUNC_list_comm, 2, '?' }, + { XFUNC_list_file, 2, CTRL('Y') }, + { XFUNC_set_mark, 1, ' ' }, + { XFUNC_kill_region, 0, CTRL('W') }, + { XFUNC_xchg_point_mark, 2, CTRL('X') }, + { XFUNC_literal, 0, CTRL('V') }, + { XFUNC_version, 1, CTRL('V') }, + { XFUNC_prev_histword, 1, '.' }, + { XFUNC_prev_histword, 1, '_' }, + { XFUNC_set_arg, 1, '0' }, + { XFUNC_set_arg, 1, '1' }, + { XFUNC_set_arg, 1, '2' }, + { XFUNC_set_arg, 1, '3' }, + { XFUNC_set_arg, 1, '4' }, + { XFUNC_set_arg, 1, '5' }, + { XFUNC_set_arg, 1, '6' }, + { XFUNC_set_arg, 1, '7' }, + { XFUNC_set_arg, 1, '8' }, + { XFUNC_set_arg, 1, '9' }, +#ifndef MKSH_SMALL + { XFUNC_fold_upper, 1, 'U' }, + { XFUNC_fold_upper, 1, 'u' }, + { XFUNC_fold_lower, 1, 'L' }, + { XFUNC_fold_lower, 1, 'l' }, + { XFUNC_fold_capitalise, 1, 'C' }, + { XFUNC_fold_capitalise, 1, 'c' }, +#endif + /* These for ansi arrow keys: arguablely shouldn't be here by + * default, but its simpler/faster/smaller than using termcap + * entries. + */ + { XFUNC_meta2, 1, '[' }, + { XFUNC_meta2, 1, 'O' }, + { XFUNC_prev_com, 2, 'A' }, + { XFUNC_next_com, 2, 'B' }, + { XFUNC_mv_forw, 2, 'C' }, + { XFUNC_mv_back, 2, 'D' }, +#ifndef MKSH_SMALL + { XFUNC_vt_hack, 2, '1' }, + { XFUNC_mv_begin | 0x80, 2, '7' }, + { XFUNC_mv_begin, 2, 'H' }, + { XFUNC_mv_end | 0x80, 2, '4' }, + { XFUNC_mv_end | 0x80, 2, '8' }, + { XFUNC_mv_end, 2, 'F' }, + { XFUNC_del_char | 0x80, 2, '3' }, + { XFUNC_search_hist_up | 0x80, 2, '5' }, + { XFUNC_search_hist_dn | 0x80, 2, '6' }, + /* more non-standard ones */ + { XFUNC_edit_line, 2, 'e' } +#endif +}; + +#ifdef MKSH_SMALL +static void x_modified(void); +static void +x_modified(void) +{ + if (!modified) { + x_histp = histptr + 1; + modified = 1; + } +} +#define XFUNC_VALUE(f) (f) +#else +#define x_modified() do { \ + if (!modified) { \ + x_histp = histptr + 1; \ + modified = 1; \ + } \ +} while (/* CONSTCOND */ 0) +#define XFUNC_VALUE(f) (f & 0x7F) +#endif + +static int +x_e_getmbc(char *sbuf) +{ + int c, pos = 0; + unsigned char *buf = (unsigned char *)sbuf; + + memset(buf, 0, 4); + buf[pos++] = c = x_e_getc(); + if (c == -1) + return (-1); + if (UTFMODE) { + if ((buf[0] >= 0xC2) && (buf[0] < 0xF0)) { + c = x_e_getc(); + if (c == -1) + return (-1); + if ((c & 0xC0) != 0x80) { + x_e_ungetc(c); + return (1); + } + buf[pos++] = c; + } + if ((buf[0] >= 0xE0) && (buf[0] < 0xF0)) { + /* XXX x_e_ungetc is one-octet only */ + buf[pos++] = c = x_e_getc(); + if (c == -1) + return (-1); + } + } + return (pos); +} + +static void +x_init_prompt(void) +{ + x_col = promptlen(prompt); + x_adj_ok = 1; + prompt_redraw = 1; + if (x_col >= xx_cols) + x_col %= xx_cols; + x_displen = xx_cols - 2 - x_col; + x_adj_done = 0; + + pprompt(prompt, 0); + if (x_displen < 1) { + x_col = 0; + x_displen = xx_cols - 2; + x_e_putc2('\n'); + prompt_redraw = 0; + } +} + +static int +x_emacs(char *buf, size_t len) +{ + int c, i; + unsigned char f; + + xbp = xbuf = buf; xend = buf + len; + xlp = xcp = xep = buf; + *xcp = 0; + xlp_valid = true; + xmp = NULL; + x_curprefix = 0; + x_histp = histptr + 1; + x_last_command = XFUNC_error; + + xx_cols = x_cols; + x_init_prompt(); + + if (x_nextcmd >= 0) { + int off = source->line - x_nextcmd; + if (histptr - history >= off) + x_load_hist(histptr - off); + x_nextcmd = -1; + } + editmode = 1; + while (1) { + x_flush(); + if ((c = x_e_getc()) < 0) + return (0); + + f = x_curprefix == -1 ? XFUNC_insert : + x_tab[x_curprefix][c]; +#ifndef MKSH_SMALL + if (f & 0x80) { + f &= 0x7F; + if ((i = x_e_getc()) != '~') + x_e_ungetc(i); + } + + /* avoid bind key macro recursion */ + if (macroptr && f == XFUNC_ins_string) + f = XFUNC_insert; +#endif + + if (!(x_ftab[f].xf_flags & XF_PREFIX) && + x_last_command != XFUNC_set_arg) { + x_arg = 1; + x_arg_defaulted = 1; + } + i = c | (x_curprefix << 8); + x_curprefix = 0; + switch ((*x_ftab[f].xf_func)(i)) { + case KSTD: + if (!(x_ftab[f].xf_flags & XF_PREFIX)) + x_last_command = f; + break; + case KEOL: + i = xep - xbuf; + return (i); + case KINTR: /* special case for interrupt */ + trapsig(SIGINT); + x_mode(false); + unwind(LSHELL); + } + /* ad-hoc hack for fixing the cursor position */ + x_goto(xcp); + } +} + +static int +x_insert(int c) +{ + static int left = 0, pos, save_arg; + static char str[4]; + + /* + * Should allow tab and control chars. + */ + if (c == 0) { + invmbs: + left = 0; + x_e_putc2(7); + return (KSTD); + } + if (UTFMODE) { + if (((c & 0xC0) == 0x80) && left) { + str[pos++] = c; + if (!--left) { + str[pos] = '\0'; + x_arg = save_arg; + while (x_arg--) + x_ins(str); + } + return (KSTD); + } + if (left) { + if (x_curprefix == -1) { + /* flush invalid multibyte */ + str[pos] = '\0'; + while (save_arg--) + x_ins(str); + } + } + if ((c >= 0xC2) && (c < 0xE0)) + left = 1; + else if ((c >= 0xE0) && (c < 0xF0)) + left = 2; + else if (c > 0x7F) + goto invmbs; + else + left = 0; + if (left) { + save_arg = x_arg; + pos = 1; + str[0] = c; + return (KSTD); + } + } + left = 0; + str[0] = c; + str[1] = '\0'; + while (x_arg--) + x_ins(str); + return (KSTD); +} + +#ifndef MKSH_SMALL +static int +x_ins_string(int c) +{ + macroptr = x_atab[c >> 8][c & 255]; + /* + * we no longer need to bother checking if macroptr is + * not NULL but first char is NUL; x_e_getc() does it + */ + return (KSTD); +} +#endif + +static int +x_do_ins(const char *cp, size_t len) +{ + if (xep + len >= xend) { + x_e_putc2(7); + return (-1); + } + memmove(xcp + len, xcp, xep - xcp + 1); + memmove(xcp, cp, len); + xcp += len; + xep += len; + x_modified(); + return (0); +} + +static int +x_ins(const char *s) +{ + char *cp = xcp; + int adj = x_adj_done; + + if (x_do_ins(s, strlen(s)) < 0) + return (-1); + /* + * x_zots() may result in a call to x_adjust() + * we want xcp to reflect the new position. + */ + xlp_valid = false; + x_lastcp(); + x_adj_ok = (xcp >= xlp); + x_zots(cp); + if (adj == x_adj_done) { /* has x_adjust() been called? */ + /* no */ + cp = xlp; + while (cp > xcp) + x_bs3(&cp); + } + if (xlp == xep - 1) + x_redraw(xx_cols); + x_adj_ok = 1; + return (0); +} + +static int +x_del_back(int c MKSH_A_UNUSED) +{ + int i = 0; + + if (xcp == xbuf) { + x_e_putc2(7); + return (KSTD); + } + do { + x_goto(xcp - 1); + } while ((++i < x_arg) && (xcp != xbuf)); + x_delete(i, false); + return (KSTD); +} + +static int +x_del_char(int c MKSH_A_UNUSED) +{ + char *cp, *cp2; + int i = 0; + + cp = xcp; + while (i < x_arg) { + utf_ptradjx(cp, cp2); + if (cp2 > xep) + break; + cp = cp2; + i++; + } + + if (!i) { + x_e_putc2(7); + return (KSTD); + } + x_delete(i, false); + return (KSTD); +} + +/* Delete nc chars to the right of the cursor (including cursor position) */ +static void +x_delete(int nc, int push) +{ + int i, nb, nw; + char *cp; + + if (nc == 0) + return; + + nw = 0; + cp = xcp; + for (i = 0; i < nc; ++i) { + char *cp2; + int j; + + j = x_size2(cp, &cp2); + if (cp2 > xep) + break; + cp = cp2; + nw += j; + } + nb = cp - xcp; + /* nc = i; */ + + if (xmp != NULL && xmp > xcp) { + if (xcp + nb > xmp) + xmp = xcp; + else + xmp -= nb; + } + /* + * This lets us yank a word we have deleted. + */ + if (push) + x_push(nb); + + xep -= nb; + memmove(xcp, xcp + nb, xep - xcp + 1); /* Copies the NUL */ + x_adj_ok = 0; /* don't redraw */ + xlp_valid = false; + x_zots(xcp); + /* + * if we are already filling the line, + * there is no need to ' ','\b'. + * But if we must, make sure we do the minimum. + */ + if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) { + nw = i = (nw < i) ? nw : i; + while (i--) + x_e_putc2(' '); + if (x_col == xx_cols - 2) { + x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' '); + ++nw; + } + while (nw--) + x_e_putc2('\b'); + } + /*x_goto(xcp);*/ + x_adj_ok = 1; + xlp_valid = false; + cp = x_lastcp(); + while (cp > xcp) + x_bs3(&cp); + + x_modified(); + return; +} + +static int +x_del_bword(int c MKSH_A_UNUSED) +{ + x_delete(x_bword(), true); + return (KSTD); +} + +static int +x_mv_bword(int c MKSH_A_UNUSED) +{ + x_bword(); + return (KSTD); +} + +static int +x_mv_fword(int c MKSH_A_UNUSED) +{ + x_fword(1); + return (KSTD); +} + +static int +x_del_fword(int c MKSH_A_UNUSED) +{ + x_delete(x_fword(0), true); + return (KSTD); +} + +static int +x_bword(void) +{ + int nc = 0, nb = 0; + char *cp = xcp; + + if (cp == xbuf) { + x_e_putc2(7); + return (0); + } + while (x_arg--) { + while (cp != xbuf && is_mfs(cp[-1])) { + cp--; + nb++; + } + while (cp != xbuf && !is_mfs(cp[-1])) { + cp--; + nb++; + } + } + x_goto(cp); + for (cp = xcp; cp < (xcp + nb); ++nc) + cp += utf_ptradj(cp); + return (nc); +} + +static int +x_fword(int move) +{ + int nc = 0; + char *cp = xcp, *cp2; + + if (cp == xep) { + x_e_putc2(7); + return (0); + } + while (x_arg--) { + while (cp != xep && is_mfs(*cp)) + cp++; + while (cp != xep && !is_mfs(*cp)) + cp++; + } + for (cp2 = xcp; cp2 < cp; ++nc) + cp2 += utf_ptradj(cp2); + if (move) + x_goto(cp); + return (nc); +} + +static void +x_goto(char *cp) +{ + if (UTFMODE) + while ((cp > xbuf) && ((*cp & 0xC0) == 0x80)) + --cp; + if (cp < xbp || cp >= utf_skipcols(xbp, x_displen)) { + /* we are heading off screen */ + xcp = cp; + x_adjust(); + } else if (cp < xcp) { /* move back */ + while (cp < xcp) + x_bs3(&xcp); + } else if (cp > xcp) { /* move forward */ + while (cp > xcp) + x_zotc3(&xcp); + } +} + +static void +x_bs3(char **p) +{ + int i; + + (*p)--; + if (UTFMODE) + while (((unsigned char)**p & 0xC0) == 0x80) + (*p)--; + + i = x_size2(*p, NULL); + while (i--) + x_e_putc2('\b'); +} + +static int +x_size_str(char *cp) +{ + int size = 0; + while (*cp) + size += x_size2(cp, &cp); + return (size); +} + +static int +x_size2(char *cp, char **dcp) +{ + int c = *(unsigned char *)cp; + + if (UTFMODE && (c > 0x7F)) + return (utf_widthadj(cp, (const char **)dcp)); + if (dcp) + *dcp = cp + 1; + if (c == '\t') + return (4); /* Kludge, tabs are always four spaces. */ + if (c < ' ' || c == 0x7f) + return (2); /* control unsigned char */ + return (1); +} + +static void +x_zots(char *str) +{ + int adj = x_adj_done; + + x_lastcp(); + while (*str && str < xlp && adj == x_adj_done) + x_zotc3(&str); +} + +static void +x_zotc2(int c) +{ + if (c == '\t') { + /* Kludge, tabs are always four spaces. */ + x_e_puts(" "); + } else if (c < ' ' || c == 0x7f) { + x_e_putc2('^'); + x_e_putc2(UNCTRL(c)); + } else + x_e_putc2(c); +} + +static void +x_zotc3(char **cp) +{ + unsigned char c = **(unsigned char **)cp; + + if (c == '\t') { + /* Kludge, tabs are always four spaces. */ + x_e_puts(" "); + (*cp)++; + } else if (c < ' ' || c == 0x7f) { + x_e_putc2('^'); + x_e_putc2(UNCTRL(c)); + (*cp)++; + } else + x_e_putc3((const char **)cp); +} + +static int +x_mv_back(int c MKSH_A_UNUSED) +{ + if (xcp == xbuf) { + x_e_putc2(7); + return (KSTD); + } + while (x_arg--) { + x_goto(xcp - 1); + if (xcp == xbuf) + break; + } + return (KSTD); +} + +static int +x_mv_forw(int c MKSH_A_UNUSED) +{ + char *cp = xcp, *cp2; + + if (xcp == xep) { + x_e_putc2(7); + return (KSTD); + } + while (x_arg--) { + utf_ptradjx(cp, cp2); + if (cp2 > xep) + break; + cp = cp2; + } + x_goto(cp); + return (KSTD); +} + +static int +x_search_char_forw(int c MKSH_A_UNUSED) +{ + char *cp = xcp; + char tmp[4]; + + *xep = '\0'; + if (x_e_getmbc(tmp) < 0) { + x_e_putc2(7); + return (KSTD); + } + while (x_arg--) { + if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL && + (cp = strstr(xbuf, tmp)) == NULL) { + x_e_putc2(7); + return (KSTD); + } + } + x_goto(cp); + return (KSTD); +} + +static int +x_search_char_back(int c MKSH_A_UNUSED) +{ + char *cp = xcp, *p, tmp[4]; + bool b; + + if (x_e_getmbc(tmp) < 0) { + x_e_putc2(7); + return (KSTD); + } + for (; x_arg--; cp = p) + for (p = cp; ; ) { + if (p-- == xbuf) + p = xep; + if (p == cp) { + x_e_putc2(7); + return (KSTD); + } + if ((tmp[1] && ((p+1) > xep)) || + (tmp[2] && ((p+2) > xep))) + continue; + b = true; + if (*p != tmp[0]) + b = false; + if (b && tmp[1] && p[1] != tmp[1]) + b = false; + if (b && tmp[2] && p[2] != tmp[2]) + b = false; + if (b) + break; + } + x_goto(cp); + return (KSTD); +} + +static int +x_newline(int c MKSH_A_UNUSED) +{ + x_e_putc2('\r'); + x_e_putc2('\n'); + x_flush(); + *xep++ = '\n'; + return (KEOL); +} + +static int +x_end_of_text(int c MKSH_A_UNUSED) +{ + x_zotc2(edchars.eof); + x_putc('\r'); + x_putc('\n'); + x_flush(); + return (KEOL); +} + +static int +x_beg_hist(int c MKSH_A_UNUSED) +{ + x_load_hist(history); + return (KSTD); +} + +static int +x_end_hist(int c MKSH_A_UNUSED) +{ + x_load_hist(histptr); + return (KSTD); +} + +static int +x_prev_com(int c MKSH_A_UNUSED) +{ + x_load_hist(x_histp - x_arg); + return (KSTD); +} + +static int +x_next_com(int c MKSH_A_UNUSED) +{ + x_load_hist(x_histp + x_arg); + return (KSTD); +} + +/* Goto a particular history number obtained from argument. + * If no argument is given history 1 is probably not what you + * want so we'll simply go to the oldest one. + */ +static int +x_goto_hist(int c MKSH_A_UNUSED) +{ + if (x_arg_defaulted) + x_load_hist(history); + else + x_load_hist(histptr + x_arg - source->line); + return (KSTD); +} + +static void +x_load_hist(char **hp) +{ + int oldsize; + char *sp = NULL; + + if (hp == histptr + 1) { + sp = holdbuf; + modified = 0; + } else if (hp < history || hp > histptr) { + x_e_putc2(7); + return; + } + if (sp == NULL) + sp = *hp; + x_histp = hp; + oldsize = x_size_str(xbuf); + if (modified) + strlcpy(holdbuf, xbuf, sizeof(holdbuf)); + strlcpy(xbuf, sp, xend - xbuf); + xbp = xbuf; + xep = xcp = xbuf + strlen(xbuf); + xlp_valid = false; + if (xep <= x_lastcp()) { + x_redraw(oldsize); + } + x_goto(xep); + modified = 0; +} + +static int +x_nl_next_com(int c MKSH_A_UNUSED) +{ + x_nextcmd = source->line - (histptr - x_histp) + 1; + return (x_newline('\n')); +} + +static int +x_eot_del(int c) +{ + if (xep == xbuf && x_arg_defaulted) + return (x_end_of_text(c)); + else + return (x_del_char(c)); +} + +/* reverse incremental history search */ +static int +x_search_hist(int c) +{ + int offset = -1; /* offset of match in xbuf, else -1 */ + char pat[256 + 1]; /* pattern buffer */ + char *p = pat; + unsigned char f; + + *p = '\0'; + while (1) { + if (offset < 0) { + x_e_puts("\nI-search: "); + x_e_puts(pat); + } + x_flush(); + if ((c = x_e_getc()) < 0) + return (KSTD); + f = x_tab[0][c]; + if (c == CTRL('[')) { + if ((f & 0x7F) == XFUNC_meta1) { + if ((c = x_e_getc()) < 0) + return (KSTD); + f = x_tab[1][c] & 0x7F; + if (f == XFUNC_meta1 || f == XFUNC_meta2) + x_meta1(CTRL('[')); + x_e_ungetc(c); + } + break; + } +#ifndef MKSH_SMALL + if (f & 0x80) { + f &= 0x7F; + if ((c = x_e_getc()) != '~') + x_e_ungetc(c); + } +#endif + if (f == XFUNC_search_hist) + offset = x_search(pat, 0, offset); + else if (f == XFUNC_del_back) { + if (p == pat) { + offset = -1; + break; + } + if (p > pat) + *--p = '\0'; + if (p == pat) + offset = -1; + else + offset = x_search(pat, 1, offset); + continue; + } else if (f == XFUNC_insert) { + /* add char to pattern */ + /* overflow check... */ + if (p >= &pat[sizeof(pat) - 1]) { + x_e_putc2(7); + continue; + } + *p++ = c, *p = '\0'; + if (offset >= 0) { + /* already have partial match */ + offset = x_match(xbuf, pat); + if (offset >= 0) { + x_goto(xbuf + offset + (p - pat) - + (*pat == '^')); + continue; + } + } + offset = x_search(pat, 0, offset); + } else if (f == XFUNC_abort) { + if (offset >= 0) + x_load_hist(histptr + 1); + break; + } else { /* other command */ + x_e_ungetc(c); + break; + } + } + if (offset < 0) + x_redraw(-1); + return (KSTD); +} + +/* search backward from current line */ +static int +x_search(char *pat, int sameline, int offset) +{ + char **hp; + int i; + + for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) { + i = x_match(*hp, pat); + if (i >= 0) { + if (offset < 0) + x_e_putc2('\n'); + x_load_hist(hp); + x_goto(xbuf + i + strlen(pat) - (*pat == '^')); + return (i); + } + } + x_e_putc2(7); + x_histp = histptr; + return (-1); +} + +#ifndef MKSH_SMALL +/* anchored search up from current line */ +static int +x_search_hist_up(int c MKSH_A_UNUSED) +{ + return (x_search_dir(-1)); +} + +/* anchored search down from current line */ +static int +x_search_hist_dn(int c MKSH_A_UNUSED) +{ + return (x_search_dir(1)); +} + +/* anchored search in the indicated direction */ +static int +x_search_dir(int search_dir /* should've been bool */) +{ + char **hp = x_histp + search_dir; + size_t curs = xcp - xbuf; + + while (histptr >= hp && hp >= history) { + if (strncmp(xbuf, *hp, curs) == 0) { + x_load_hist(hp); + x_goto(xbuf + curs); + break; + } + hp += search_dir; + } + return (KSTD); +} +#endif + +/* return position of first match of pattern in string, else -1 */ +static int +x_match(char *str, char *pat) +{ + if (*pat == '^') { + return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1); + } else { + char *q = strstr(str, pat); + return ((q == NULL) ? -1 : q - str); + } +} + +static int +x_del_line(int c MKSH_A_UNUSED) +{ + int i, j; + + *xep = 0; + i = xep - xbuf; + j = x_size_str(xbuf); + xcp = xbuf; + x_push(i); + xlp = xbp = xep = xbuf; + xlp_valid = true; + *xcp = 0; + xmp = NULL; + x_redraw(j); + x_modified(); + return (KSTD); +} + +static int +x_mv_end(int c MKSH_A_UNUSED) +{ + x_goto(xep); + return (KSTD); +} + +static int +x_mv_begin(int c MKSH_A_UNUSED) +{ + x_goto(xbuf); + return (KSTD); +} + +static int +x_draw_line(int c MKSH_A_UNUSED) +{ + x_redraw(-1); + return (KSTD); +} + +static int +x_e_rebuildline(const char *clrstr) +{ + shf_puts(clrstr, shl_out); + x_adjust(); + return (KSTD); +} + +static int +x_cls(int c MKSH_A_UNUSED) +{ + return (x_e_rebuildline(MKSH_CLS_STRING)); +} + +/* Redraw (part of) the line. If limit is < 0, the everything is redrawn + * on a NEW line, otherwise limit is the screen column up to which needs + * redrawing. + */ +static void +x_redraw(int limit) +{ + int i, j, x_trunc = 0; + char *cp; + + x_adj_ok = 0; + if (limit == -1) + x_e_putc2('\n'); + else + x_e_putc2('\r'); + x_flush(); + if (xbp == xbuf) { + x_col = promptlen(prompt); + if (x_col >= xx_cols) + x_trunc = (x_col / xx_cols) * xx_cols; + if (prompt_redraw) + pprompt(prompt, x_trunc); + } + if (x_col >= xx_cols) + x_col %= xx_cols; + x_displen = xx_cols - 2 - x_col; + if (x_displen < 1) { + x_col = 0; + x_displen = xx_cols - 2; + } + xlp_valid = false; + x_lastcp(); + x_zots(xbp); + if (xbp != xbuf || xep > xlp) + limit = xx_cols; + if (limit >= 0) { + if (xep > xlp) + i = 0; /* we fill the line */ + else { + char *cpl = xbp; + + i = limit; + while (cpl < xlp) + i -= x_size2(cpl, &cpl); + } + + j = 0; + while ((j < i) || (x_col < (xx_cols - 2))) { + if (!(x_col < (xx_cols - 2))) + break; + x_e_putc2(' '); + j++; + } + i = ' '; + if (xep > xlp) { /* more off screen */ + if (xbp > xbuf) + i = '*'; + else + i = '>'; + } else if (xbp > xbuf) + i = '<'; + x_e_putc2(i); + j++; + while (j--) + x_e_putc2('\b'); + } + cp = xlp; + while (cp > xcp) + x_bs3(&cp); + x_adj_ok = 1; + return; +} + +static int +x_transpose(int c MKSH_A_UNUSED) +{ + unsigned int tmpa, tmpb; + + /* What transpose is meant to do seems to be up for debate. This + * is a general summary of the options; the text is abcd with the + * upper case character or underscore indicating the cursor position: + * Who Before After Before After + * AT&T ksh in emacs mode: abCd abdC abcd_ (bell) + * AT&T ksh in gmacs mode: abCd baCd abcd_ abdc_ + * gnu emacs: abCd acbD abcd_ abdc_ + * Pdksh currently goes with GNU behavior since I believe this is the + * most common version of emacs, unless in gmacs mode, in which case + * it does the AT&T ksh gmacs mode. + * This should really be broken up into 3 functions so users can bind + * to the one they want. + */ + if (xcp == xbuf) { + x_e_putc2(7); + return (KSTD); + } else if (xcp == xep || Flag(FGMACS)) { + if (xcp - xbuf == 1) { + x_e_putc2(7); + return (KSTD); + } + /* Gosling/Unipress emacs style: Swap two characters before the + * cursor, do not change cursor position + */ + x_bs3(&xcp); + if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { + x_e_putc2(7); + return (KSTD); + } + x_bs3(&xcp); + if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) { + x_e_putc2(7); + return (KSTD); + } + utf_wctomb(xcp, tmpa); + x_zotc3(&xcp); + utf_wctomb(xcp, tmpb); + x_zotc3(&xcp); + } else { + /* GNU emacs style: Swap the characters before and under the + * cursor, move cursor position along one. + */ + if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { + x_e_putc2(7); + return (KSTD); + } + x_bs3(&xcp); + if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) { + x_e_putc2(7); + return (KSTD); + } + utf_wctomb(xcp, tmpa); + x_zotc3(&xcp); + utf_wctomb(xcp, tmpb); + x_zotc3(&xcp); + } + x_modified(); + return (KSTD); +} + +static int +x_literal(int c MKSH_A_UNUSED) +{ + x_curprefix = -1; + return (KSTD); +} + +static int +x_meta1(int c MKSH_A_UNUSED) +{ + x_curprefix = 1; + return (KSTD); +} + +static int +x_meta2(int c MKSH_A_UNUSED) +{ + x_curprefix = 2; + return (KSTD); +} + +static int +x_kill(int c MKSH_A_UNUSED) +{ + int col = xcp - xbuf; + int lastcol = xep - xbuf; + int ndel; + + if (x_arg_defaulted) + x_arg = lastcol; + else if (x_arg > lastcol) + x_arg = lastcol; + ndel = x_arg - col; + if (ndel < 0) { + x_goto(xbuf + x_arg); + ndel = -ndel; + } + x_delete(ndel, true); + return (KSTD); +} + +static void +x_push(int nchars) +{ + char *cp; + + strndupx(cp, xcp, nchars, AEDIT); + if (killstack[killsp]) + afree(killstack[killsp], AEDIT); + killstack[killsp] = cp; + killsp = (killsp + 1) % KILLSIZE; +} + +static int +x_yank(int c MKSH_A_UNUSED) +{ + if (killsp == 0) + killtp = KILLSIZE; + else + killtp = killsp; + killtp--; + if (killstack[killtp] == 0) { + x_e_puts("\nnothing to yank"); + x_redraw(-1); + return (KSTD); + } + xmp = xcp; + x_ins(killstack[killtp]); + return (KSTD); +} + +static int +x_meta_yank(int c MKSH_A_UNUSED) +{ + int len; + + if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) || + killstack[killtp] == 0) { + killtp = killsp; + x_e_puts("\nyank something first"); + x_redraw(-1); + return (KSTD); + } + len = strlen(killstack[killtp]); + x_goto(xcp - len); + x_delete(len, false); + do { + if (killtp == 0) + killtp = KILLSIZE - 1; + else + killtp--; + } while (killstack[killtp] == 0); + x_ins(killstack[killtp]); + return (KSTD); +} + +static int +x_abort(int c MKSH_A_UNUSED) +{ + /* x_zotc(c); */ + xlp = xep = xcp = xbp = xbuf; + xlp_valid = true; + *xcp = 0; + x_modified(); + return (KINTR); +} + +static int +x_error(int c MKSH_A_UNUSED) +{ + x_e_putc2(7); + return (KSTD); +} + +#ifndef MKSH_SMALL +/* special VT100 style key sequence hack */ +static int +x_vt_hack(int c) +{ + /* we only support PF2-'1' for now */ + if (c != (2 << 8 | '1')) + return (x_error(c)); + + /* what's the next character? */ + switch ((c = x_e_getc())) { + case '~': + x_arg = 1; + x_arg_defaulted = 1; + return (x_mv_begin(0)); + case ';': + /* "interesting" sequence detected */ + break; + default: + goto unwind_err; + } + + /* XXX x_e_ungetc is one-octet only */ + if ((c = x_e_getc()) != '5' && c != '3') + goto unwind_err; + + /*- + * At this point, we have read the following octets so far: + * - ESC+[ or ESC+O or Ctrl-X (Præfix 2) + * - 1 (vt_hack) + * - ; + * - 5 (Ctrl key combiner) or 3 (Alt key combiner) + * We can now accept one more octet designating the key. + */ + + switch ((c = x_e_getc())) { + case 'C': + return (x_mv_fword(c)); + case 'D': + return (x_mv_bword(c)); + } + + unwind_err: + x_e_ungetc(c); + return (x_error(c)); +} +#endif + +static char * +x_mapin(const char *cp, Area *ap) +{ + char *news, *op; + + /* for clang's static analyser, the nonnull attribute isn't enough */ + mkssert(cp != NULL); + + strdupx(news, cp, ap); + op = news; + while (*cp) { + /* XXX -- should handle \^ escape? */ + if (*cp == '^') { + cp++; + if (*cp >= '?') /* includes '?'; ASCII */ + *op++ = CTRL(*cp); + else { + *op++ = '^'; + cp--; + } + } else + *op++ = *cp; + cp++; + } + *op = '\0'; + + return (news); +} + +static void +x_mapout2(int c, char **buf) +{ + char *p = *buf; + + if (c < ' ' || c == 0x7f) { + *p++ = '^'; + *p++ = UNCTRL(c); + } else + *p++ = c; + *p = 0; + *buf = p; +} + +static char * +x_mapout(int c) +{ + static char buf[8]; + char *bp = buf; + + x_mapout2(c, &bp); + return (buf); +} + +static void +x_print(int prefix, int key) +{ + int f = x_tab[prefix][key]; + + if (prefix) + /* prefix == 1 || prefix == 2 */ + shf_puts(x_mapout(prefix == 1 ? + CTRL('[') : CTRL('X')), shl_stdout); +#ifdef MKSH_SMALL + shprintf("%s = ", x_mapout(key)); +#else + shprintf("%s%s = ", x_mapout(key), (f & 0x80) ? "~" : ""); + if (XFUNC_VALUE(f) != XFUNC_ins_string) +#endif + shprintf("%s\n", x_ftab[XFUNC_VALUE(f)].xf_name); +#ifndef MKSH_SMALL + else + shprintf("'%s'\n", x_atab[prefix][key]); +#endif +} + +int +x_bind(const char *a1, const char *a2, +#ifndef MKSH_SMALL + bool macro, /* bind -m */ +#endif + bool list) /* bind -l */ +{ + unsigned char f; + int prefix, key; + char *m1, *m2; +#ifndef MKSH_SMALL + char *sp = NULL; + bool hastilde; +#endif + + if (x_tab == NULL) { + bi_errorf("cannot bind, not a tty"); + return (1); + } + /* List function names */ + if (list) { + for (f = 0; f < NELEM(x_ftab); f++) + if (x_ftab[f].xf_name && + !(x_ftab[f].xf_flags & XF_NOBIND)) + shprintf("%s\n", x_ftab[f].xf_name); + return (0); + } + if (a1 == NULL) { + for (prefix = 0; prefix < X_NTABS; prefix++) + for (key = 0; key < X_TABSZ; key++) { + f = XFUNC_VALUE(x_tab[prefix][key]); + if (f == XFUNC_insert || f == XFUNC_error +#ifndef MKSH_SMALL + || (macro && f != XFUNC_ins_string) +#endif + ) + continue; + x_print(prefix, key); + } + return (0); + } + m2 = m1 = x_mapin(a1, ATEMP); + prefix = 0; + for (;; m1++) { + key = (unsigned char)*m1; + f = XFUNC_VALUE(x_tab[prefix][key]); + if (f == XFUNC_meta1) + prefix = 1; + else if (f == XFUNC_meta2) + prefix = 2; + else + break; + } + if (*++m1 +#ifndef MKSH_SMALL + && ((*m1 != '~') || *(m1 + 1)) +#endif + ) { + char msg[256] = "key sequence '"; + const char *c = a1; + m1 = msg + strlen(msg); + while (*c && m1 < (msg + sizeof(msg) - 3)) + x_mapout2(*c++, &m1); + bi_errorf("%s' too long", msg); + return (1); + } +#ifndef MKSH_SMALL + hastilde = *m1; +#endif + afree(m2, ATEMP); + + if (a2 == NULL) { + x_print(prefix, key); + return (0); + } + if (*a2 == 0) { + f = XFUNC_insert; +#ifndef MKSH_SMALL + } else if (macro) { + f = XFUNC_ins_string; + sp = x_mapin(a2, AEDIT); +#endif + } else { + for (f = 0; f < NELEM(x_ftab); f++) + if (x_ftab[f].xf_name && + strcmp(x_ftab[f].xf_name, a2) == 0) + break; + if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) { + bi_errorf("%s: no such function", a2); + return (1); + } + } + +#ifndef MKSH_SMALL + if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string && + x_atab[prefix][key]) + afree(x_atab[prefix][key], AEDIT); +#endif + x_tab[prefix][key] = f +#ifndef MKSH_SMALL + | (hastilde ? 0x80 : 0) +#endif + ; +#ifndef MKSH_SMALL + x_atab[prefix][key] = sp; +#endif + + /* Track what the user has bound so x_mode(true) won't toast things */ + if (f == XFUNC_insert) + x_bound[(prefix * X_TABSZ + key) / 8] &= + ~(1 << ((prefix * X_TABSZ + key) % 8)); + else + x_bound[(prefix * X_TABSZ + key) / 8] |= + (1 << ((prefix * X_TABSZ + key) % 8)); + + return (0); +} + +static void +x_init_emacs(void) +{ + int i, j; + + ainit(AEDIT); + x_nextcmd = -1; + + x_tab = alloc(X_NTABS * sizeof(*x_tab), AEDIT); + for (j = 0; j < X_TABSZ; j++) + x_tab[0][j] = XFUNC_insert; + for (i = 1; i < X_NTABS; i++) + for (j = 0; j < X_TABSZ; j++) + x_tab[i][j] = XFUNC_error; + for (i = 0; i < (int)NELEM(x_defbindings); i++) + x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] + = x_defbindings[i].xdb_func; + +#ifndef MKSH_SMALL + x_atab = alloc(X_NTABS * sizeof(*x_atab), AEDIT); + for (i = 1; i < X_NTABS; i++) + for (j = 0; j < X_TABSZ; j++) + x_atab[i][j] = NULL; +#endif +} + +static void +bind_if_not_bound(int p, int k, int func) +{ + /* Has user already bound this key? If so, don't override it */ + if (x_bound[((p) * X_TABSZ + (k)) / 8] & + (1 << (((p) * X_TABSZ + (k)) % 8))) + return; + + x_tab[p][k] = func; +} + +static int +x_set_mark(int c MKSH_A_UNUSED) +{ + xmp = xcp; + return (KSTD); +} + +static int +x_kill_region(int c MKSH_A_UNUSED) +{ + int rsize; + char *xr; + + if (xmp == NULL) { + x_e_putc2(7); + return (KSTD); + } + if (xmp > xcp) { + rsize = xmp - xcp; + xr = xcp; + } else { + rsize = xcp - xmp; + xr = xmp; + } + x_goto(xr); + x_delete(rsize, true); + xmp = xr; + return (KSTD); +} + +static int +x_xchg_point_mark(int c MKSH_A_UNUSED) +{ + char *tmp; + + if (xmp == NULL) { + x_e_putc2(7); + return (KSTD); + } + tmp = xmp; + xmp = xcp; + x_goto(tmp); + return (KSTD); +} + +static int +x_noop(int c MKSH_A_UNUSED) +{ + return (KSTD); +} + +/* + * File/command name completion routines + */ +static int +x_comp_comm(int c MKSH_A_UNUSED) +{ + do_complete(XCF_COMMAND, CT_COMPLETE); + return (KSTD); +} + +static int +x_list_comm(int c MKSH_A_UNUSED) +{ + do_complete(XCF_COMMAND, CT_LIST); + return (KSTD); +} + +static int +x_complete(int c MKSH_A_UNUSED) +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLETE); + return (KSTD); +} + +static int +x_enumerate(int c MKSH_A_UNUSED) +{ + do_complete(XCF_COMMAND_FILE, CT_LIST); + return (KSTD); +} + +static int +x_comp_file(int c MKSH_A_UNUSED) +{ + do_complete(XCF_FILE, CT_COMPLETE); + return (KSTD); +} + +static int +x_list_file(int c MKSH_A_UNUSED) +{ + do_complete(XCF_FILE, CT_LIST); + return (KSTD); +} + +static int +x_comp_list(int c MKSH_A_UNUSED) +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLIST); + return (KSTD); +} + +static int +x_expand(int c MKSH_A_UNUSED) +{ + char **words; + int start, end, nwords, i; + bool is_command; + + nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + + if (nwords == 0) { + x_e_putc2(7); + return (KSTD); + } + x_goto(xbuf + start); + x_delete(end - start, false); + for (i = 0; i < nwords;) { + if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 || + (++i < nwords && x_ins(" ") < 0)) { + x_e_putc2(7); + return (KSTD); + } + } + x_adjust(); + + return (KSTD); +} + +/* type == 0 for list, 1 for complete and 2 for complete-list */ +static void +do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ + Comp_type type) +{ + char **words; + int start, end, nlen, olen, nwords; + bool is_command, completed = false; + + nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + /* no match */ + if (nwords == 0) { + x_e_putc2(7); + return; + } + if (type == CT_LIST) { + x_print_expansions(nwords, words, is_command); + x_redraw(0); + x_free_words(nwords, words); + return; + } + olen = end - start; + nlen = x_longest_prefix(nwords, words); + /* complete */ + if (nwords == 1 || nlen > olen) { + x_goto(xbuf + start); + x_delete(olen, false); + x_escape(words[0], nlen, x_do_ins); + x_adjust(); + completed = true; + } + /* add space if single non-dir match */ + if (nwords == 1 && words[0][nlen - 1] != '/') { + x_ins(" "); + completed = true; + } + if (type == CT_COMPLIST && !completed) { + x_print_expansions(nwords, words, is_command); + completed = true; + } + if (completed) + x_redraw(0); + + x_free_words(nwords, words); +} + +/* NAME: + * x_adjust - redraw the line adjusting starting point etc. + * + * DESCRIPTION: + * This function is called when we have exceeded the bounds + * of the edit window. It increments x_adj_done so that + * functions like x_ins and x_delete know that we have been + * called and can skip the x_bs() stuff which has already + * been done by x_redraw. + * + * RETURN VALUE: + * None + */ +static void +x_adjust(void) +{ + x_adj_done++; /* flag the fact that we were called. */ + /* + * we had a problem if the prompt length > xx_cols / 2 + */ + if ((xbp = xcp - (x_displen / 2)) < xbuf) + xbp = xbuf; + if (UTFMODE) + while ((xbp > xbuf) && ((*xbp & 0xC0) == 0x80)) + --xbp; + xlp_valid = false; + x_redraw(xx_cols); + x_flush(); +} + +static void +x_e_ungetc(int c) +{ + unget_char = c < 0 ? -1 : (c & 255); +} + +static int +x_e_getc(void) +{ + int c; + + if (unget_char >= 0) { + c = unget_char; + unget_char = -1; + return (c); + } + +#ifndef MKSH_SMALL + if (macroptr) { + if ((c = (unsigned char)*macroptr++)) + return (c); + macroptr = NULL; + } +#endif + + return (x_getc()); +} + +static void +x_e_putc2(int c) +{ + int width = 1; + + if (c == '\r' || c == '\n') + x_col = 0; + if (x_col < xx_cols) { + if (UTFMODE && (c > 0x7F)) { + char utf_tmp[3]; + size_t x; + + if (c < 0xA0) + c = 0xFFFD; + x = utf_wctomb(utf_tmp, c); + x_putc(utf_tmp[0]); + if (x > 1) + x_putc(utf_tmp[1]); + if (x > 2) + x_putc(utf_tmp[2]); + width = utf_wcwidth(c); + } else + x_putc(c); + switch (c) { + case 7: + break; + case '\r': + case '\n': + break; + case '\b': + x_col--; + break; + default: + x_col += width; + break; + } + } + if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2))) + x_adjust(); +} + +static void +x_e_putc3(const char **cp) +{ + int width = 1, c = **(const unsigned char **)cp; + + if (c == '\r' || c == '\n') + x_col = 0; + if (x_col < xx_cols) { + if (UTFMODE && (c > 0x7F)) { + char *cp2; + + width = utf_widthadj(*cp, (const char **)&cp2); + while (*cp < cp2) + x_putcf(*(*cp)++); + } else { + (*cp)++; + x_putc(c); + } + switch (c) { + case 7: + break; + case '\r': + case '\n': + break; + case '\b': + x_col--; + break; + default: + x_col += width; + break; + } + } + if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2))) + x_adjust(); +} + +static void +x_e_puts(const char *s) +{ + int adj = x_adj_done; + + while (*s && adj == x_adj_done) + x_e_putc3(&s); +} + +/* NAME: + * x_set_arg - set an arg value for next function + * + * DESCRIPTION: + * This is a simple implementation of M-[0-9]. + * + * RETURN VALUE: + * KSTD + */ +static int +x_set_arg(int c) +{ + int n = 0, first = 1; + + c &= 255; /* strip command prefix */ + for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0) + n = n * 10 + (c - '0'); + if (c < 0 || first) { + x_e_putc2(7); + x_arg = 1; + x_arg_defaulted = 1; + } else { + x_e_ungetc(c); + x_arg = n; + x_arg_defaulted = 0; + } + return (KSTD); +} + +/* Comment or uncomment the current line. */ +static int +x_comment(int c MKSH_A_UNUSED) +{ + int oldsize = x_size_str(xbuf); + int len = xep - xbuf; + int ret = x_do_comment(xbuf, xend - xbuf, &len); + + if (ret < 0) + x_e_putc2(7); + else { + x_modified(); + xep = xbuf + len; + *xep = '\0'; + xcp = xbp = xbuf; + x_redraw(oldsize); + if (ret > 0) + return (x_newline('\n')); + } + return (KSTD); +} + +static int +x_version(int c MKSH_A_UNUSED) +{ + char *o_xbuf = xbuf, *o_xend = xend; + char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp; + int vlen, lim = x_lastcp() - xbp; + char *v; + + strdupx(v, KSH_VERSION, ATEMP); + + xbuf = xbp = xcp = v; + xend = xep = v + (vlen = strlen(v)); + x_redraw(lim); + x_flush(); + + c = x_e_getc(); + xbuf = o_xbuf; + xend = o_xend; + xbp = o_xbp; + xep = o_xep; + xcp = o_xcp; + x_redraw(vlen); + + if (c < 0) + return (KSTD); + /* This is what AT&T ksh seems to do... Very bizarre */ + if (c != ' ') + x_e_ungetc(c); + + afree(v, ATEMP); + return (KSTD); +} + +#ifndef MKSH_SMALL +static int +x_edit_line(int c MKSH_A_UNUSED) +{ + if (x_arg_defaulted) { + if (xep == xbuf) { + x_e_putc2(7); + return (KSTD); + } + if (modified) { + *xep = '\0'; + histsave(&source->line, xbuf, true, true); + x_arg = 0; + } else + x_arg = source->line - (histptr - x_histp); + } + if (x_arg) + shf_snprintf(xbuf, xend - xbuf, "%s %d", + "fc -e ${VISUAL:-${EDITOR:-vi}} --", x_arg); + else + strlcpy(xbuf, "fc -e ${VISUAL:-${EDITOR:-vi}} --", xend - xbuf); + xep = xbuf + strlen(xbuf); + return (x_newline('\n')); +} +#endif + +/* NAME: + * x_prev_histword - recover word from prev command + * + * DESCRIPTION: + * This function recovers the last word from the previous + * command and inserts it into the current edit line. If a + * numeric arg is supplied then the n'th word from the + * start of the previous command is used. + * As a side effect, trashes the mark in order to achieve + * being called in a repeatable fashion. + * + * Bound to M-. + * + * RETURN VALUE: + * KSTD + */ +static int +x_prev_histword(int c MKSH_A_UNUSED) +{ + char *rcp, *cp; + char **xhp; + int m; + + if (xmp && modified > 1) + x_kill_region(0); + m = modified ? modified : 1; + xhp = histptr - (m - 1); + if ((xhp < history) || !(cp = *xhp)) { + x_e_putc2(7); + x_modified(); + return (KSTD); + } + x_set_mark(0); + if (x_arg_defaulted) { + rcp = &cp[strlen(cp) - 1]; + /* + * ignore white-space after the last word + */ + while (rcp > cp && is_cfs(*rcp)) + rcp--; + while (rcp > cp && !is_cfs(*rcp)) + rcp--; + if (is_cfs(*rcp)) + rcp++; + x_ins(rcp); + } else { + char ch; + + rcp = cp; + /* + * ignore white-space at start of line + */ + while (*rcp && is_cfs(*rcp)) + rcp++; + while (x_arg-- > 1) { + while (*rcp && !is_cfs(*rcp)) + rcp++; + while (*rcp && is_cfs(*rcp)) + rcp++; + } + cp = rcp; + while (*rcp && !is_cfs(*rcp)) + rcp++; + ch = *rcp; + *rcp = '\0'; + x_ins(cp); + *rcp = ch; + } + modified = m + 1; + return (KSTD); +} + +#ifndef MKSH_SMALL +/* Uppercase N(1) words */ +static int +x_fold_upper(int c MKSH_A_UNUSED) +{ + return (x_fold_case('U')); +} + +/* Lowercase N(1) words */ +static int +x_fold_lower(int c MKSH_A_UNUSED) +{ + return (x_fold_case('L')); +} + +/* Lowercase N(1) words */ +static int +x_fold_capitalise(int c MKSH_A_UNUSED) +{ + return (x_fold_case('C')); +} + +/* NAME: + * x_fold_case - convert word to UPPER/lower/Capital case + * + * DESCRIPTION: + * This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c + * to UPPER case, lower case or Capitalise words. + * + * RETURN VALUE: + * None + */ +static int +x_fold_case(int c) +{ + char *cp = xcp; + + if (cp == xep) { + x_e_putc2(7); + return (KSTD); + } + while (x_arg--) { + /* + * first skip over any white-space + */ + while (cp != xep && is_mfs(*cp)) + cp++; + /* + * do the first char on its own since it may be + * a different action than for the rest. + */ + if (cp != xep) { + if (c == 'L') /* lowercase */ + *cp = ksh_tolower(*cp); + else /* uppercase, capitalise */ + *cp = ksh_toupper(*cp); + cp++; + } + /* + * now for the rest of the word + */ + while (cp != xep && !is_mfs(*cp)) { + if (c == 'U') /* uppercase */ + *cp = ksh_toupper(*cp); + else /* lowercase, capitalise */ + *cp = ksh_tolower(*cp); + cp++; + } + } + x_goto(cp); + x_modified(); + return (KSTD); +} +#endif + +/* NAME: + * x_lastcp - last visible char + * + * SYNOPSIS: + * x_lastcp() + * + * DESCRIPTION: + * This function returns a pointer to that char in the + * edit buffer that will be the last displayed on the + * screen. The sequence: + * + * cp = x_lastcp(); + * while (cp > xcp) + * x_bs3(&cp); + * + * Will position the cursor correctly on the screen. + * + * RETURN VALUE: + * cp or NULL + */ +static char * +x_lastcp(void) +{ + if (!xlp_valid) { + int i = 0, j; + char *xlp2; + + xlp = xbp; + while (xlp < xep) { + j = x_size2(xlp, &xlp2); + if ((i + j) > x_displen) + break; + i += j; + xlp = xlp2; + } + } + xlp_valid = true; + return (xlp); +} + +static bool +x_mode(bool onoff) +{ + static bool x_cur_mode; + bool prev; + + if (x_cur_mode == onoff) + return (x_cur_mode); + prev = x_cur_mode; + x_cur_mode = onoff; + + if (onoff) { + struct termios cb; + + cb = tty_state; + + edchars.erase = cb.c_cc[VERASE]; + edchars.kill = cb.c_cc[VKILL]; + edchars.intr = cb.c_cc[VINTR]; + edchars.quit = cb.c_cc[VQUIT]; + edchars.eof = cb.c_cc[VEOF]; +#ifdef VWERASE + edchars.werase = cb.c_cc[VWERASE]; +#endif + cb.c_iflag &= ~(INLCR | ICRNL); + cb.c_lflag &= ~(ISIG | ICANON | ECHO); +#if defined(VLNEXT) && defined(_POSIX_VDISABLE) + /* osf/1 processes lnext when ~icanon */ + cb.c_cc[VLNEXT] = _POSIX_VDISABLE; +#endif + /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ +#if defined(VDISCARD) && defined(_POSIX_VDISABLE) + cb.c_cc[VDISCARD] = _POSIX_VDISABLE; +#endif + cb.c_cc[VTIME] = 0; + cb.c_cc[VMIN] = 1; + + tcsetattr(tty_fd, TCSADRAIN, &cb); + +#ifdef _POSIX_VDISABLE + /* Convert unset values to internal 'unset' value */ + if (edchars.erase == _POSIX_VDISABLE) + edchars.erase = -1; + if (edchars.kill == _POSIX_VDISABLE) + edchars.kill = -1; + if (edchars.intr == _POSIX_VDISABLE) + edchars.intr = -1; + if (edchars.quit == _POSIX_VDISABLE) + edchars.quit = -1; + if (edchars.eof == _POSIX_VDISABLE) + edchars.eof = -1; + if (edchars.werase == _POSIX_VDISABLE) + edchars.werase = -1; +#endif + + if (edchars.erase >= 0) { + bind_if_not_bound(0, edchars.erase, XFUNC_del_back); + bind_if_not_bound(1, edchars.erase, XFUNC_del_bword); + } + if (edchars.kill >= 0) + bind_if_not_bound(0, edchars.kill, XFUNC_del_line); + if (edchars.werase >= 0) + bind_if_not_bound(0, edchars.werase, XFUNC_del_bword); + if (edchars.intr >= 0) + bind_if_not_bound(0, edchars.intr, XFUNC_abort); + if (edchars.quit >= 0) + bind_if_not_bound(0, edchars.quit, XFUNC_noop); + } else + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + + return (prev); +} + +#if !MKSH_S_NOVI +/* +++ vi editing mode +++ */ + +#define Ctrl(c) (c&0x1f) + +struct edstate { + char *cbuf; + int winleft; + int cbufsize; + int linelen; + int cursor; +}; + +static int vi_hook(int); +static int nextstate(int); +static int vi_insert(int); +static int vi_cmd(int, const char *); +static int domove(int, const char *, int); +static int redo_insert(int); +static void yank_range(int, int); +static int bracktype(int); +static void save_cbuf(void); +static void restore_cbuf(void); +static int putbuf(const char *, int, int); +static void del_range(int, int); +static int findch(int, int, int, int); +static int forwword(int); +static int backword(int); +static int endword(int); +static int Forwword(int); +static int Backword(int); +static int Endword(int); +static int grabhist(int, int); +static int grabsearch(int, int, int, char *); +static void redraw_line(int); +static void refresh(int); +static int outofwin(void); +static void rewindow(void); +static int newcol(int, int); +static void display(char *, char *, int); +static void ed_mov_opt(int, char *); +static int expand_word(int); +static int complete_word(int, int); +static int print_expansions(struct edstate *, int); +#define char_len(c) ((c) < ' ' || (c) == 0x7F ? 2 : 1) +static void x_vi_zotc(int); +static void vi_error(void); +static void vi_macro_reset(void); +static int x_vi_putbuf(const char *, size_t); + +#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */ +#define M_ 0x2 /* movement command (h, l, etc.) */ +#define E_ 0x4 /* extended command (c, d, y) */ +#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */ +#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */ +#define B_ 0x20 /* bad command (^@) */ +#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */ +#define S_ 0x80 /* search (/, ?) */ + +#define is_bad(c) (classify[(c)&0x7f]&B_) +#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_)) +#define is_move(c) (classify[(c)&0x7f]&M_) +#define is_extend(c) (classify[(c)&0x7f]&E_) +#define is_long(c) (classify[(c)&0x7f]&X_) +#define is_undoable(c) (!(classify[(c)&0x7f]&U_)) +#define is_srch(c) (classify[(c)&0x7f]&S_) +#define is_zerocount(c) (classify[(c)&0x7f]&Z_) + +static const unsigned char classify[128] = { +/* 0 1 2 3 4 5 6 7 */ +/* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */ + B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0, +/* 1 ^H ^I ^J ^K ^L ^M ^N ^O */ + M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0, +/* 2 ^P ^Q ^R ^S ^T ^U ^V ^W */ + C_, 0, C_|U_, 0, 0, 0, C_, 0, +/* 3 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ + C_, 0, 0, C_|Z_, 0, 0, 0, 0, +/* 4 <space> ! " # $ % & ' */ + M_, 0, 0, C_, M_, M_, 0, 0, +/* 5 ( ) * + , - . / */ + 0, 0, C_, C_, M_, C_, 0, C_|S_, +/* 6 0 1 2 3 4 5 6 7 */ + M_, 0, 0, 0, 0, 0, 0, 0, +/* 7 8 9 : ; < = > ? */ + 0, 0, 0, M_, 0, C_, 0, C_|S_, +/* 8 @ A B C D E F G */ + C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_, +/* 9 H I J K L M N O */ + 0, C_, 0, 0, 0, 0, C_|U_, 0, +/* A P Q R S T U V W */ + C_, 0, C_, C_, M_|X_, C_, 0, M_, +/* B X Y Z [ \ ] ^ _ */ + C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_, +/* C ` a b c d e f g */ + 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_, +/* D h i j k l m n o */ + M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0, +/* E p q r s t u v w */ + C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_, M_, +/* F x y z { | } ~ ^? */ + C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0 +}; + +#define MAXVICMD 3 +#define SRCHLEN 40 + +#define INSERT 1 +#define REPLACE 2 + +#define VNORMAL 0 /* command, insert or replace mode */ +#define VARG1 1 /* digit prefix (first, eg, 5l) */ +#define VEXTCMD 2 /* cmd + movement (eg, cl) */ +#define VARG2 3 /* digit prefix (second, eg, 2c3l) */ +#define VXCH 4 /* f, F, t, T, @ */ +#define VFAIL 5 /* bad command */ +#define VCMD 6 /* single char command (eg, X) */ +#define VREDO 7 /* . */ +#define VLIT 8 /* ^V */ +#define VSEARCH 9 /* /, ? */ +#define VVERSION 10 /* <ESC> ^V */ + +static char undocbuf[LINE]; + +static struct edstate *save_edstate(struct edstate *old); +static void restore_edstate(struct edstate *old, struct edstate *news); +static void free_edstate(struct edstate *old); + +static struct edstate ebuf; +static struct edstate undobuf = { undocbuf, 0, LINE, 0, 0 }; + +static struct edstate *es; /* current editor state */ +static struct edstate *undo; + +static char ibuf[LINE]; /* input buffer */ +static int first_insert; /* set when starting in insert mode */ +static int saved_inslen; /* saved inslen for first insert */ +static int inslen; /* length of input buffer */ +static int srchlen; /* length of current search pattern */ +static char ybuf[LINE]; /* yank buffer */ +static int yanklen; /* length of yank buffer */ +static int fsavecmd = ' '; /* last find command */ +static int fsavech; /* character to find */ +static char lastcmd[MAXVICMD]; /* last non-move command */ +static int lastac; /* argcnt for lastcmd */ +static int lastsearch = ' '; /* last search command */ +static char srchpat[SRCHLEN]; /* last search pattern */ +static int insert; /* non-zero in insert mode */ +static int hnum; /* position in history */ +static int ohnum; /* history line copied (after mod) */ +static int hlast; /* 1 past last position in history */ +static int state; + +/* Information for keeping track of macros that are being expanded. + * The format of buf is the alias contents followed by a NUL byte followed + * by the name (letter) of the alias. The end of the buffer is marked by + * a double NUL. The name of the alias is stored so recursive macros can + * be detected. + */ +struct macro_state { + unsigned char *p; /* current position in buf */ + unsigned char *buf; /* pointer to macro(s) being expanded */ + int len; /* how much data in buffer */ +}; +static struct macro_state macro; + +enum expand_mode { + NONE, EXPAND, COMPLETE, PRINT +}; +static enum expand_mode expanded = NONE; /* last input was expanded */ + +static int +x_vi(char *buf, size_t len) +{ + int c; + + state = VNORMAL; + ohnum = hnum = hlast = histnum(-1) + 1; + insert = INSERT; + saved_inslen = inslen; + first_insert = 1; + inslen = 0; + vi_macro_reset(); + + es = &ebuf; + es->cbuf = buf; + undo = &undobuf; + undo->cbufsize = es->cbufsize = len > LINE ? LINE : len; + + es->linelen = undo->linelen = 0; + es->cursor = undo->cursor = 0; + es->winleft = undo->winleft = 0; + + cur_col = promptlen(prompt); + prompt_trunc = (cur_col / x_cols) * x_cols; + cur_col -= prompt_trunc; + + pprompt(prompt, 0); + if (cur_col > x_cols - 3 - MIN_EDIT_SPACE) { + prompt_redraw = cur_col = 0; + x_putc('\n'); + } else + prompt_redraw = 1; + pwidth = cur_col; + + if (!wbuf_len || wbuf_len != x_cols - 3) { + wbuf_len = x_cols - 3; + wbuf[0] = aresize(wbuf[0], wbuf_len, APERM); + wbuf[1] = aresize(wbuf[1], wbuf_len, APERM); + } + (void)memset(wbuf[0], ' ', wbuf_len); + (void)memset(wbuf[1], ' ', wbuf_len); + winwidth = x_cols - pwidth - 3; + win = 0; + morec = ' '; + lastref = 1; + holdlen = 0; + + editmode = 2; + x_flush(); + while (1) { + if (macro.p) { + c = *macro.p++; + /* end of current macro? */ + if (!c) { + /* more macros left to finish? */ + if (*macro.p++) + continue; + /* must be the end of all the macros */ + vi_macro_reset(); + c = x_getc(); + } + } else + c = x_getc(); + + if (c == -1) + break; + if (state != VLIT) { + if (c == edchars.intr || c == edchars.quit) { + /* pretend we got an interrupt */ + x_vi_zotc(c); + x_flush(); + trapsig(c == edchars.intr ? SIGINT : SIGQUIT); + x_mode(false); + unwind(LSHELL); + } else if (c == edchars.eof && state != VVERSION) { + if (es->linelen == 0) { + x_vi_zotc(edchars.eof); + c = -1; + break; + } + continue; + } + } + if (vi_hook(c)) + break; + x_flush(); + } + + x_putc('\r'); + x_putc('\n'); + x_flush(); + + if (c == -1 || (ssize_t)len <= es->linelen) + return (-1); + + if (es->cbuf != buf) + memmove(buf, es->cbuf, es->linelen); + + buf[es->linelen++] = '\n'; + + return (es->linelen); +} + +static int +vi_hook(int ch) +{ + static char curcmd[MAXVICMD], locpat[SRCHLEN]; + static int cmdlen, argc1, argc2; + + switch (state) { + + case VNORMAL: + if (insert != 0) { + if (ch == Ctrl('v')) { + state = VLIT; + ch = '^'; + } + switch (vi_insert(ch)) { + case -1: + vi_error(); + state = VNORMAL; + break; + case 0: + if (state == VLIT) { + es->cursor--; + refresh(0); + } else + refresh(insert != 0); + break; + case 1: + return (1); + } + } else { + if (ch == '\r' || ch == '\n') + return (1); + cmdlen = 0; + argc1 = 0; + if (ch >= '1' && ch <= '9') { + argc1 = ch - '0'; + state = VARG1; + } else { + curcmd[cmdlen++] = ch; + state = nextstate(ch); + if (state == VSEARCH) { + save_cbuf(); + es->cursor = 0; + es->linelen = 0; + if (ch == '/') { + if (putbuf("/", 1, 0) != 0) + return (-1); + } else if (putbuf("?", 1, 0) != 0) + return (-1); + refresh(0); + } + if (state == VVERSION) { + save_cbuf(); + es->cursor = 0; + es->linelen = 0; + putbuf(KSH_VERSION, + strlen(KSH_VERSION), 0); + refresh(0); + } + } + } + break; + + case VLIT: + if (is_bad(ch)) { + del_range(es->cursor, es->cursor + 1); + vi_error(); + } else + es->cbuf[es->cursor++] = ch; + refresh(1); + state = VNORMAL; + break; + + case VVERSION: + restore_cbuf(); + state = VNORMAL; + refresh(0); + break; + + case VARG1: + if (ksh_isdigit(ch)) + argc1 = argc1 * 10 + ch - '0'; + else { + curcmd[cmdlen++] = ch; + state = nextstate(ch); + } + break; + + case VEXTCMD: + argc2 = 0; + if (ch >= '1' && ch <= '9') { + argc2 = ch - '0'; + state = VARG2; + return (0); + } else { + curcmd[cmdlen++] = ch; + if (ch == curcmd[0]) + state = VCMD; + else if (is_move(ch)) + state = nextstate(ch); + else + state = VFAIL; + } + break; + + case VARG2: + if (ksh_isdigit(ch)) + argc2 = argc2 * 10 + ch - '0'; + else { + if (argc1 == 0) + argc1 = argc2; + else + argc1 *= argc2; + curcmd[cmdlen++] = ch; + if (ch == curcmd[0]) + state = VCMD; + else if (is_move(ch)) + state = nextstate(ch); + else + state = VFAIL; + } + break; + + case VXCH: + if (ch == Ctrl('[')) + state = VNORMAL; + else { + curcmd[cmdlen++] = ch; + state = VCMD; + } + break; + + case VSEARCH: + if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) { + restore_cbuf(); + /* Repeat last search? */ + if (srchlen == 0) { + if (!srchpat[0]) { + vi_error(); + state = VNORMAL; + refresh(0); + return (0); + } + } else { + locpat[srchlen] = '\0'; + memcpy(srchpat, locpat, srchlen + 1); + } + state = VCMD; + } else if (ch == edchars.erase || ch == Ctrl('h')) { + if (srchlen != 0) { + srchlen--; + es->linelen -= char_len((unsigned char)locpat[srchlen]); + es->cursor = es->linelen; + refresh(0); + return (0); + } + restore_cbuf(); + state = VNORMAL; + refresh(0); + } else if (ch == edchars.kill) { + srchlen = 0; + es->linelen = 1; + es->cursor = 1; + refresh(0); + return (0); + } else if (ch == edchars.werase) { + int i, n = srchlen; + struct edstate new_es, *save_es; + + new_es.cursor = n; + new_es.cbuf = locpat; + + save_es = es; + es = &new_es; + n = backword(1); + es = save_es; + + for (i = srchlen; --i >= n; ) + es->linelen -= char_len((unsigned char)locpat[i]); + srchlen = n; + es->cursor = es->linelen; + refresh(0); + return (0); + } else { + if (srchlen == SRCHLEN - 1) + vi_error(); + else { + locpat[srchlen++] = ch; + if (ch < ' ' || ch == 0x7f) { + if (es->linelen + 2 > es->cbufsize) + vi_error(); + es->cbuf[es->linelen++] = '^'; + es->cbuf[es->linelen++] = ch ^ '@'; + } else { + if (es->linelen >= es->cbufsize) + vi_error(); + es->cbuf[es->linelen++] = ch; + } + es->cursor = es->linelen; + refresh(0); + } + return (0); + } + break; + } + + switch (state) { + case VCMD: + state = VNORMAL; + switch (vi_cmd(argc1, curcmd)) { + case -1: + vi_error(); + refresh(0); + break; + case 0: + if (insert != 0) + inslen = 0; + refresh(insert != 0); + break; + case 1: + refresh(0); + return (1); + case 2: + /* back from a 'v' command - don't redraw the screen */ + return (1); + } + break; + + case VREDO: + state = VNORMAL; + if (argc1 != 0) + lastac = argc1; + switch (vi_cmd(lastac, lastcmd)) { + case -1: + vi_error(); + refresh(0); + break; + case 0: + if (insert != 0) { + if (lastcmd[0] == 's' || lastcmd[0] == 'c' || + lastcmd[0] == 'C') { + if (redo_insert(1) != 0) + vi_error(); + } else { + if (redo_insert(lastac) != 0) + vi_error(); + } + } + refresh(0); + break; + case 1: + refresh(0); + return (1); + case 2: + /* back from a 'v' command - can't happen */ + break; + } + break; + + case VFAIL: + state = VNORMAL; + vi_error(); + break; + } + return (0); +} + +static int +nextstate(int ch) +{ + if (is_extend(ch)) + return (VEXTCMD); + else if (is_srch(ch)) + return (VSEARCH); + else if (is_long(ch)) + return (VXCH); + else if (ch == '.') + return (VREDO); + else if (ch == Ctrl('v')) + return (VVERSION); + else if (is_cmd(ch)) + return (VCMD); + else + return (VFAIL); +} + +static int +vi_insert(int ch) +{ + int tcursor; + + if (ch == edchars.erase || ch == Ctrl('h')) { + if (insert == REPLACE) { + if (es->cursor == undo->cursor) { + vi_error(); + return (0); + } + if (inslen > 0) + inslen--; + es->cursor--; + if (es->cursor >= undo->linelen) + es->linelen--; + else + es->cbuf[es->cursor] = undo->cbuf[es->cursor]; + } else { + if (es->cursor == 0) + return (0); + if (inslen > 0) + inslen--; + es->cursor--; + es->linelen--; + memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor + 1], + es->linelen - es->cursor + 1); + } + expanded = NONE; + return (0); + } + if (ch == edchars.kill) { + if (es->cursor != 0) { + inslen = 0; + memmove(es->cbuf, &es->cbuf[es->cursor], + es->linelen - es->cursor); + es->linelen -= es->cursor; + es->cursor = 0; + } + expanded = NONE; + return (0); + } + if (ch == edchars.werase) { + if (es->cursor != 0) { + tcursor = backword(1); + memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor], + es->linelen - es->cursor); + es->linelen -= es->cursor - tcursor; + if (inslen < es->cursor - tcursor) + inslen = 0; + else + inslen -= es->cursor - tcursor; + es->cursor = tcursor; + } + expanded = NONE; + return (0); + } + /* If any chars are entered before escape, trash the saved insert + * buffer (if user inserts & deletes char, ibuf gets trashed and + * we don't want to use it) + */ + if (first_insert && ch != Ctrl('[')) + saved_inslen = 0; + switch (ch) { + case '\0': + return (-1); + + case '\r': + case '\n': + return (1); + + case Ctrl('['): + expanded = NONE; + if (first_insert) { + first_insert = 0; + if (inslen == 0) { + inslen = saved_inslen; + return (redo_insert(0)); + } + lastcmd[0] = 'a'; + lastac = 1; + } + if (lastcmd[0] == 's' || lastcmd[0] == 'c' || + lastcmd[0] == 'C') + return (redo_insert(0)); + else + return (redo_insert(lastac - 1)); + + /* { Begin nonstandard vi commands */ + case Ctrl('x'): + expand_word(0); + break; + + case Ctrl('f'): + complete_word(0, 0); + break; + + case Ctrl('e'): + print_expansions(es, 0); + break; + + case Ctrl('i'): + if (Flag(FVITABCOMPLETE)) { + complete_word(0, 0); + break; + } + /* FALLTHROUGH */ + /* End nonstandard vi commands } */ + + default: + if (es->linelen >= es->cbufsize - 1) + return (-1); + ibuf[inslen++] = ch; + if (insert == INSERT) { + memmove(&es->cbuf[es->cursor + 1], &es->cbuf[es->cursor], + es->linelen - es->cursor); + es->linelen++; + } + es->cbuf[es->cursor++] = ch; + if (insert == REPLACE && es->cursor > es->linelen) + es->linelen++; + expanded = NONE; + } + return (0); +} + +static int +vi_cmd(int argcnt, const char *cmd) +{ + int ncursor; + int cur, c1, c2, c3 = 0; + int any; + struct edstate *t; + + if (argcnt == 0 && !is_zerocount(*cmd)) + argcnt = 1; + + if (is_move(*cmd)) { + if ((cur = domove(argcnt, cmd, 0)) >= 0) { + if (cur == es->linelen && cur != 0) + cur--; + es->cursor = cur; + } else + return (-1); + } else { + /* Don't save state in middle of macro.. */ + if (is_undoable(*cmd) && !macro.p) { + undo->winleft = es->winleft; + memmove(undo->cbuf, es->cbuf, es->linelen); + undo->linelen = es->linelen; + undo->cursor = es->cursor; + lastac = argcnt; + memmove(lastcmd, cmd, MAXVICMD); + } + switch (*cmd) { + + case Ctrl('l'): + case Ctrl('r'): + redraw_line(1); + break; + + case '@': + { + static char alias[] = "_\0"; + struct tbl *ap; + int olen, nlen; + char *p, *nbuf; + + /* lookup letter in alias list... */ + alias[1] = cmd[1]; + ap = ktsearch(&aliases, alias, hash(alias)); + if (!cmd[1] || !ap || !(ap->flag & ISSET)) + return (-1); + /* check if this is a recursive call... */ + if ((p = (char *)macro.p)) + while ((p = strnul(p)) && p[1]) + if (*++p == cmd[1]) + return (-1); + /* insert alias into macro buffer */ + nlen = strlen(ap->val.s) + 1; + olen = !macro.p ? 2 : + macro.len - (macro.p - macro.buf); + nbuf = alloc(nlen + 1 + olen, APERM); + memcpy(nbuf, ap->val.s, nlen); + nbuf[nlen++] = cmd[1]; + if (macro.p) { + memcpy(nbuf + nlen, macro.p, olen); + afree(macro.buf, APERM); + nlen += olen; + } else { + nbuf[nlen++] = '\0'; + nbuf[nlen++] = '\0'; + } + macro.p = macro.buf = (unsigned char *)nbuf; + macro.len = nlen; + } + break; + + case 'a': + modified = 1; + hnum = hlast; + if (es->linelen != 0) + es->cursor++; + insert = INSERT; + break; + + case 'A': + modified = 1; + hnum = hlast; + del_range(0, 0); + es->cursor = es->linelen; + insert = INSERT; + break; + + case 'S': + es->cursor = domove(1, "^", 1); + del_range(es->cursor, es->linelen); + modified = 1; + hnum = hlast; + insert = INSERT; + break; + + case 'Y': + cmd = "y$"; + /* ahhhhhh... */ + case 'c': + case 'd': + case 'y': + if (*cmd == cmd[1]) { + c1 = *cmd == 'c' ? domove(1, "^", 1) : 0; + c2 = es->linelen; + } else if (!is_move(cmd[1])) + return (-1); + else { + if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0) + return (-1); + if (*cmd == 'c' && + (cmd[1] == 'w' || cmd[1] == 'W') && + !ksh_isspace(es->cbuf[es->cursor])) { + do { + --ncursor; + } while (ksh_isspace(es->cbuf[ncursor])); + ncursor++; + } + if (ncursor > es->cursor) { + c1 = es->cursor; + c2 = ncursor; + } else { + c1 = ncursor; + c2 = es->cursor; + if (cmd[1] == '%') + c2++; + } + } + if (*cmd != 'c' && c1 != c2) + yank_range(c1, c2); + if (*cmd != 'y') { + del_range(c1, c2); + es->cursor = c1; + } + if (*cmd == 'c') { + modified = 1; + hnum = hlast; + insert = INSERT; + } + break; + + case 'p': + modified = 1; + hnum = hlast; + if (es->linelen != 0) + es->cursor++; + while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0) + ; + if (es->cursor != 0) + es->cursor--; + if (argcnt != 0) + return (-1); + break; + + case 'P': + modified = 1; + hnum = hlast; + any = 0; + while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0) + any = 1; + if (any && es->cursor != 0) + es->cursor--; + if (argcnt != 0) + return (-1); + break; + + case 'C': + modified = 1; + hnum = hlast; + del_range(es->cursor, es->linelen); + insert = INSERT; + break; + + case 'D': + yank_range(es->cursor, es->linelen); + del_range(es->cursor, es->linelen); + if (es->cursor != 0) + es->cursor--; + break; + + case 'g': + if (!argcnt) + argcnt = hlast; + /* FALLTHROUGH */ + case 'G': + if (!argcnt) + argcnt = 1; + else + argcnt = hlast - (source->line - argcnt); + if (grabhist(modified, argcnt - 1) < 0) + return (-1); + else { + modified = 0; + hnum = argcnt - 1; + } + break; + + case 'i': + modified = 1; + hnum = hlast; + insert = INSERT; + break; + + case 'I': + modified = 1; + hnum = hlast; + es->cursor = domove(1, "^", 1); + insert = INSERT; + break; + + case 'j': + case '+': + case Ctrl('n'): + if (grabhist(modified, hnum + argcnt) < 0) + return (-1); + else { + modified = 0; + hnum += argcnt; + } + break; + + case 'k': + case '-': + case Ctrl('p'): + if (grabhist(modified, hnum - argcnt) < 0) + return (-1); + else { + modified = 0; + hnum -= argcnt; + } + break; + + case 'r': + if (es->linelen == 0) + return (-1); + modified = 1; + hnum = hlast; + if (cmd[1] == 0) + vi_error(); + else { + int n; + + if (es->cursor + argcnt > es->linelen) + return (-1); + for (n = 0; n < argcnt; ++n) + es->cbuf[es->cursor + n] = cmd[1]; + es->cursor += n - 1; + } + break; + + case 'R': + modified = 1; + hnum = hlast; + insert = REPLACE; + break; + + case 's': + if (es->linelen == 0) + return (-1); + modified = 1; + hnum = hlast; + if (es->cursor + argcnt > es->linelen) + argcnt = es->linelen - es->cursor; + del_range(es->cursor, es->cursor + argcnt); + insert = INSERT; + break; + + case 'v': + if (!argcnt) { + if (es->linelen == 0) + return (-1); + if (modified) { + es->cbuf[es->linelen] = '\0'; + histsave(&source->line, es->cbuf, true, + true); + } else + argcnt = source->line + 1 - + (hlast - hnum); + } + if (argcnt) + shf_snprintf(es->cbuf, es->cbufsize, "%s %d", + "fc -e ${VISUAL:-${EDITOR:-vi}} --", + argcnt); + else + strlcpy(es->cbuf, + "fc -e ${VISUAL:-${EDITOR:-vi}} --", + es->cbufsize); + es->linelen = strlen(es->cbuf); + return (2); + + case 'x': + if (es->linelen == 0) + return (-1); + modified = 1; + hnum = hlast; + if (es->cursor + argcnt > es->linelen) + argcnt = es->linelen - es->cursor; + yank_range(es->cursor, es->cursor + argcnt); + del_range(es->cursor, es->cursor + argcnt); + break; + + case 'X': + if (es->cursor > 0) { + modified = 1; + hnum = hlast; + if (es->cursor < argcnt) + argcnt = es->cursor; + yank_range(es->cursor - argcnt, es->cursor); + del_range(es->cursor - argcnt, es->cursor); + es->cursor -= argcnt; + } else + return (-1); + break; + + case 'u': + t = es; + es = undo; + undo = t; + break; + + case 'U': + if (!modified) + return (-1); + if (grabhist(modified, ohnum) < 0) + return (-1); + modified = 0; + hnum = ohnum; + break; + + case '?': + if (hnum == hlast) + hnum = -1; + /* ahhh */ + case '/': + c3 = 1; + srchlen = 0; + lastsearch = *cmd; + /* FALLTHROUGH */ + case 'n': + case 'N': + if (lastsearch == ' ') + return (-1); + if (lastsearch == '?') + c1 = 1; + else + c1 = 0; + if (*cmd == 'N') + c1 = !c1; + if ((c2 = grabsearch(modified, hnum, + c1, srchpat)) < 0) { + if (c3) { + restore_cbuf(); + refresh(0); + } + return (-1); + } else { + modified = 0; + hnum = c2; + ohnum = hnum; + } + break; + case '_': + { + int inspace; + char *p, *sp; + + if (histnum(-1) < 0) + return (-1); + p = *histpos(); +#define issp(c) (ksh_isspace(c) || (c) == '\n') + if (argcnt) { + while (*p && issp(*p)) + p++; + while (*p && --argcnt) { + while (*p && !issp(*p)) + p++; + while (*p && issp(*p)) + p++; + } + if (!*p) + return (-1); + sp = p; + } else { + sp = p; + inspace = 0; + while (*p) { + if (issp(*p)) + inspace = 1; + else if (inspace) { + inspace = 0; + sp = p; + } + p++; + } + p = sp; + } + modified = 1; + hnum = hlast; + if (es->cursor != es->linelen) + es->cursor++; + while (*p && !issp(*p)) { + argcnt++; + p++; + } + if (putbuf(" ", 1, 0) != 0) + argcnt = -1; + else if (putbuf(sp, argcnt, 0) != 0) + argcnt = -1; + if (argcnt < 0) { + if (es->cursor != 0) + es->cursor--; + return (-1); + } + insert = INSERT; + } + break; + + case '~': + { + char *p; + int i; + + if (es->linelen == 0) + return (-1); + for (i = 0; i < argcnt; i++) { + p = &es->cbuf[es->cursor]; + if (ksh_islower(*p)) { + modified = 1; + hnum = hlast; + *p = ksh_toupper(*p); + } else if (ksh_isupper(*p)) { + modified = 1; + hnum = hlast; + *p = ksh_tolower(*p); + } + if (es->cursor < es->linelen - 1) + es->cursor++; + } + break; + } + + case '#': + { + int ret = x_do_comment(es->cbuf, es->cbufsize, + &es->linelen); + if (ret >= 0) + es->cursor = 0; + return (ret); + } + + case '=': /* AT&T ksh */ + case Ctrl('e'): /* Nonstandard vi/ksh */ + print_expansions(es, 1); + break; + + + case Ctrl('i'): /* Nonstandard vi/ksh */ + if (!Flag(FVITABCOMPLETE)) + return (-1); + complete_word(1, argcnt); + break; + + case Ctrl('['): /* some annoying AT&T kshs */ + if (!Flag(FVIESCCOMPLETE)) + return (-1); + case '\\': /* AT&T ksh */ + case Ctrl('f'): /* Nonstandard vi/ksh */ + complete_word(1, argcnt); + break; + + + case '*': /* AT&T ksh */ + case Ctrl('x'): /* Nonstandard vi/ksh */ + expand_word(1); + break; + } + if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen) + es->cursor--; + } + return (0); +} + +static int +domove(int argcnt, const char *cmd, int sub) +{ + int bcount, i = 0, t; + int ncursor = 0; + + switch (*cmd) { + case 'b': + if (!sub && es->cursor == 0) + return (-1); + ncursor = backword(argcnt); + break; + + case 'B': + if (!sub && es->cursor == 0) + return (-1); + ncursor = Backword(argcnt); + break; + + case 'e': + if (!sub && es->cursor + 1 >= es->linelen) + return (-1); + ncursor = endword(argcnt); + if (sub && ncursor < es->linelen) + ncursor++; + break; + + case 'E': + if (!sub && es->cursor + 1 >= es->linelen) + return (-1); + ncursor = Endword(argcnt); + if (sub && ncursor < es->linelen) + ncursor++; + break; + + case 'f': + case 'F': + case 't': + case 'T': + fsavecmd = *cmd; + fsavech = cmd[1]; + /* drop through */ + + case ',': + case ';': + if (fsavecmd == ' ') + return (-1); + i = fsavecmd == 'f' || fsavecmd == 'F'; + t = fsavecmd > 'a'; + if (*cmd == ',') + t = !t; + if ((ncursor = findch(fsavech, argcnt, t, i)) < 0) + return (-1); + if (sub && t) + ncursor++; + break; + + case 'h': + case Ctrl('h'): + if (!sub && es->cursor == 0) + return (-1); + ncursor = es->cursor - argcnt; + if (ncursor < 0) + ncursor = 0; + break; + + case ' ': + case 'l': + if (!sub && es->cursor + 1 >= es->linelen) + return (-1); + if (es->linelen != 0) { + ncursor = es->cursor + argcnt; + if (ncursor > es->linelen) + ncursor = es->linelen; + } + break; + + case 'w': + if (!sub && es->cursor + 1 >= es->linelen) + return (-1); + ncursor = forwword(argcnt); + break; + + case 'W': + if (!sub && es->cursor + 1 >= es->linelen) + return (-1); + ncursor = Forwword(argcnt); + break; + + case '0': + ncursor = 0; + break; + + case '^': + ncursor = 0; + while (ncursor < es->linelen - 1 && + ksh_isspace(es->cbuf[ncursor])) + ncursor++; + break; + + case '|': + ncursor = argcnt; + if (ncursor > es->linelen) + ncursor = es->linelen; + if (ncursor) + ncursor--; + break; + + case '$': + if (es->linelen != 0) + ncursor = es->linelen; + else + ncursor = 0; + break; + + case '%': + ncursor = es->cursor; + while (ncursor < es->linelen && + (i = bracktype(es->cbuf[ncursor])) == 0) + ncursor++; + if (ncursor == es->linelen) + return (-1); + bcount = 1; + do { + if (i > 0) { + if (++ncursor >= es->linelen) + return (-1); + } else { + if (--ncursor < 0) + return (-1); + } + t = bracktype(es->cbuf[ncursor]); + if (t == i) + bcount++; + else if (t == -i) + bcount--; + } while (bcount != 0); + if (sub && i > 0) + ncursor++; + break; + + default: + return (-1); + } + return (ncursor); +} + +static int +redo_insert(int count) +{ + while (count-- > 0) + if (putbuf(ibuf, inslen, insert == REPLACE) != 0) + return (-1); + if (es->cursor > 0) + es->cursor--; + insert = 0; + return (0); +} + +static void +yank_range(int a, int b) +{ + yanklen = b - a; + if (yanklen != 0) + memmove(ybuf, &es->cbuf[a], yanklen); +} + +static int +bracktype(int ch) +{ + switch (ch) { + + case '(': + return (1); + + case '[': + return (2); + + case '{': + return (3); + + case ')': + return (-1); + + case ']': + return (-2); + + case '}': + return (-3); + + default: + return (0); + } +} + +/* + * Non user interface editor routines below here + */ + +static void +save_cbuf(void) +{ + memmove(holdbuf, es->cbuf, es->linelen); + holdlen = es->linelen; + holdbuf[holdlen] = '\0'; +} + +static void +restore_cbuf(void) +{ + es->cursor = 0; + es->linelen = holdlen; + memmove(es->cbuf, holdbuf, holdlen); +} + +/* return a new edstate */ +static struct edstate * +save_edstate(struct edstate *old) +{ + struct edstate *news; + + news = alloc(sizeof(struct edstate), APERM); + news->cbuf = alloc(old->cbufsize, APERM); + memcpy(news->cbuf, old->cbuf, old->linelen); + news->cbufsize = old->cbufsize; + news->linelen = old->linelen; + news->cursor = old->cursor; + news->winleft = old->winleft; + return (news); +} + +static void +restore_edstate(struct edstate *news, struct edstate *old) +{ + memcpy(news->cbuf, old->cbuf, old->linelen); + news->linelen = old->linelen; + news->cursor = old->cursor; + news->winleft = old->winleft; + free_edstate(old); +} + +static void +free_edstate(struct edstate *old) +{ + afree(old->cbuf, APERM); + afree(old, APERM); +} + +/* + * this is used for calling x_escape() in complete_word() + */ +static int +x_vi_putbuf(const char *s, size_t len) +{ + return (putbuf(s, len, 0)); +} + +static int +putbuf(const char *buf, int len, int repl) +{ + if (len == 0) + return (0); + if (repl) { + if (es->cursor + len >= es->cbufsize) + return (-1); + if (es->cursor + len > es->linelen) + es->linelen = es->cursor + len; + } else { + if (es->linelen + len >= es->cbufsize) + return (-1); + memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor], + es->linelen - es->cursor); + es->linelen += len; + } + memmove(&es->cbuf[es->cursor], buf, len); + es->cursor += len; + return (0); +} + +static void +del_range(int a, int b) +{ + if (es->linelen != b) + memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b); + es->linelen -= b - a; +} + +static int +findch(int ch, int cnt, int forw, int incl) +{ + int ncursor; + + if (es->linelen == 0) + return (-1); + ncursor = es->cursor; + while (cnt--) { + do { + if (forw) { + if (++ncursor == es->linelen) + return (-1); + } else { + if (--ncursor < 0) + return (-1); + } + } while (es->cbuf[ncursor] != ch); + } + if (!incl) { + if (forw) + ncursor--; + else + ncursor++; + } + return (ncursor); +} + +static int +forwword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen && argcnt--) { + if (ksh_isalnux(es->cbuf[ncursor])) + while (ksh_isalnux(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + else if (!ksh_isspace(es->cbuf[ncursor])) + while (!ksh_isalnux(es->cbuf[ncursor]) && + !ksh_isspace(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + while (ksh_isspace(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + } + return (ncursor); +} + +static int +backword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor > 0 && argcnt--) { + while (--ncursor > 0 && ksh_isspace(es->cbuf[ncursor])) + ; + if (ncursor > 0) { + if (ksh_isalnux(es->cbuf[ncursor])) + while (--ncursor >= 0 && + ksh_isalnux(es->cbuf[ncursor])) + ; + else + while (--ncursor >= 0 && + !ksh_isalnux(es->cbuf[ncursor]) && + !ksh_isspace(es->cbuf[ncursor])) + ; + ncursor++; + } + } + return (ncursor); +} + +static int +endword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen && argcnt--) { + while (++ncursor < es->linelen - 1 && + ksh_isspace(es->cbuf[ncursor])) + ; + if (ncursor < es->linelen - 1) { + if (ksh_isalnux(es->cbuf[ncursor])) + while (++ncursor < es->linelen && + ksh_isalnux(es->cbuf[ncursor])) + ; + else + while (++ncursor < es->linelen && + !ksh_isalnux(es->cbuf[ncursor]) && + !ksh_isspace(es->cbuf[ncursor])) + ; + ncursor--; + } + } + return (ncursor); +} + +static int +Forwword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen && argcnt--) { + while (!ksh_isspace(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + while (ksh_isspace(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + } + return (ncursor); +} + +static int +Backword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor > 0 && argcnt--) { + while (--ncursor >= 0 && ksh_isspace(es->cbuf[ncursor])) + ; + while (ncursor >= 0 && !ksh_isspace(es->cbuf[ncursor])) + ncursor--; + ncursor++; + } + return (ncursor); +} + +static int +Endword(int argcnt) +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen - 1 && argcnt--) { + while (++ncursor < es->linelen - 1 && + ksh_isspace(es->cbuf[ncursor])) + ; + if (ncursor < es->linelen - 1) { + while (++ncursor < es->linelen && + !ksh_isspace(es->cbuf[ncursor])) + ; + ncursor--; + } + } + return (ncursor); +} + +static int +grabhist(int save, int n) +{ + char *hptr; + + if (n < 0 || n > hlast) + return (-1); + if (n == hlast) { + restore_cbuf(); + ohnum = n; + return (0); + } + (void)histnum(n); + if ((hptr = *histpos()) == NULL) { + internal_warningf("grabhist: bad history array"); + return (-1); + } + if (save) + save_cbuf(); + if ((es->linelen = strlen(hptr)) >= es->cbufsize) + es->linelen = es->cbufsize - 1; + memmove(es->cbuf, hptr, es->linelen); + es->cursor = 0; + ohnum = n; + return (0); +} + +static int +grabsearch(int save, int start, int fwd, char *pat) +{ + char *hptr; + int hist; + int anchored; + + if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1)) + return (-1); + if (fwd) + start++; + else + start--; + anchored = *pat == '^' ? (++pat, 1) : 0; + if ((hist = findhist(start, fwd, pat, anchored)) < 0) { + /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */ + /* XXX should strcmp be strncmp? */ + if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) { + restore_cbuf(); + return (0); + } else + return (-1); + } + if (save) + save_cbuf(); + histnum(hist); + hptr = *histpos(); + if ((es->linelen = strlen(hptr)) >= es->cbufsize) + es->linelen = es->cbufsize - 1; + memmove(es->cbuf, hptr, es->linelen); + es->cursor = 0; + return (hist); +} + +static void +redraw_line(int newl) +{ + (void)memset(wbuf[win], ' ', wbuf_len); + if (newl) { + x_putc('\r'); + x_putc('\n'); + } + if (prompt_redraw) + pprompt(prompt, prompt_trunc); + cur_col = pwidth; + morec = ' '; +} + +static void +refresh(int leftside) +{ + if (leftside < 0) + leftside = lastref; + else + lastref = leftside; + if (outofwin()) + rewindow(); + display(wbuf[1 - win], wbuf[win], leftside); + win = 1 - win; +} + +static int +outofwin(void) +{ + int cur, col; + + if (es->cursor < es->winleft) + return (1); + col = 0; + cur = es->winleft; + while (cur < es->cursor) + col = newcol((unsigned char)es->cbuf[cur++], col); + if (col >= winwidth) + return (1); + return (0); +} + +static void +rewindow(void) +{ + int tcur, tcol; + int holdcur1, holdcol1; + int holdcur2, holdcol2; + + holdcur1 = holdcur2 = tcur = 0; + holdcol1 = holdcol2 = tcol = 0; + while (tcur < es->cursor) { + if (tcol - holdcol2 > winwidth / 2) { + holdcur1 = holdcur2; + holdcol1 = holdcol2; + holdcur2 = tcur; + holdcol2 = tcol; + } + tcol = newcol((unsigned char)es->cbuf[tcur++], tcol); + } + while (tcol - holdcol1 > winwidth / 2) + holdcol1 = newcol((unsigned char)es->cbuf[holdcur1++], + holdcol1); + es->winleft = holdcur1; +} + +static int +newcol(int ch, int col) +{ + if (ch == '\t') + return ((col | 7) + 1); + return (col + char_len(ch)); +} + +static void +display(char *wb1, char *wb2, int leftside) +{ + unsigned char ch; + char *twb1, *twb2, mc; + int cur, col, cnt; + int ncol = 0; + int moreright; + + col = 0; + cur = es->winleft; + moreright = 0; + twb1 = wb1; + while (col < winwidth && cur < es->linelen) { + if (cur == es->cursor && leftside) + ncol = col + pwidth; + if ((ch = es->cbuf[cur]) == '\t') + do { + *twb1++ = ' '; + } while (++col < winwidth && (col & 7) != 0); + else if (col < winwidth) { + if (ch < ' ' || ch == 0x7f) { + *twb1++ = '^'; + if (++col < winwidth) { + *twb1++ = ch ^ '@'; + col++; + } + } else { + *twb1++ = ch; + col++; + } + } + if (cur == es->cursor && !leftside) + ncol = col + pwidth - 1; + cur++; + } + if (cur == es->cursor) + ncol = col + pwidth; + if (col < winwidth) { + while (col < winwidth) { + *twb1++ = ' '; + col++; + } + } else + moreright++; + *twb1 = ' '; + + col = pwidth; + cnt = winwidth; + twb1 = wb1; + twb2 = wb2; + while (cnt--) { + if (*twb1 != *twb2) { + if (cur_col != col) + ed_mov_opt(col, wb1); + x_putc(*twb1); + cur_col++; + } + twb1++; + twb2++; + col++; + } + if (es->winleft > 0 && moreright) + /* POSIX says to use * for this but that is a globbing + * character and may confuse people; + is more innocuous + */ + mc = '+'; + else if (es->winleft > 0) + mc = '<'; + else if (moreright) + mc = '>'; + else + mc = ' '; + if (mc != morec) { + ed_mov_opt(pwidth + winwidth + 1, wb1); + x_putc(mc); + cur_col++; + morec = mc; + } + if (cur_col != ncol) + ed_mov_opt(ncol, wb1); +} + +static void +ed_mov_opt(int col, char *wb) +{ + if (col < cur_col) { + if (col + 1 < cur_col - col) { + x_putc('\r'); + if (prompt_redraw) + pprompt(prompt, prompt_trunc); + cur_col = pwidth; + while (cur_col++ < col) + x_putcf(*wb++); + } else { + while (cur_col-- > col) + x_putc('\b'); + } + } else { + wb = &wb[cur_col - pwidth]; + while (cur_col++ < col) + x_putcf(*wb++); + } + cur_col = col; +} + + +/* replace word with all expansions (ie, expand word*) */ +static int +expand_word(int cmd) +{ + static struct edstate *buf; + int rval = 0; + int nwords; + int start, end; + char **words; + int i; + + /* Undo previous expansion */ + if (cmd == 0 && expanded == EXPAND && buf) { + restore_edstate(es, buf); + buf = 0; + expanded = NONE; + return (0); + } + if (buf) { + free_edstate(buf); + buf = 0; + } + + nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH, + es->cbuf, es->linelen, es->cursor, + &start, &end, &words, NULL); + if (nwords == 0) { + vi_error(); + return (-1); + } + + buf = save_edstate(es); + expanded = EXPAND; + del_range(start, end); + es->cursor = start; + for (i = 0; i < nwords; ) { + if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) { + rval = -1; + break; + } + if (++i < nwords && putbuf(" ", 1, 0) != 0) { + rval = -1; + break; + } + } + i = buf->cursor - end; + if (rval == 0 && i > 0) + es->cursor += i; + modified = 1; + hnum = hlast; + insert = INSERT; + lastac = 0; + refresh(0); + return (rval); +} + +static int +complete_word(int cmd, int count) +{ + static struct edstate *buf; + int rval, nwords, start, end, match_len; + char **words; + char *match; + bool is_command, is_unique; + + /* Undo previous completion */ + if (cmd == 0 && expanded == COMPLETE && buf) { + print_expansions(buf, 0); + expanded = PRINT; + return (0); + } + if (cmd == 0 && expanded == PRINT && buf) { + restore_edstate(es, buf); + buf = 0; + expanded = NONE; + return (0); + } + if (buf) { + free_edstate(buf); + buf = 0; + } + + /* XCF_FULLPATH for count 'cause the menu printed by print_expansions() + * was done this way. + */ + nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0), + es->cbuf, es->linelen, es->cursor, + &start, &end, &words, &is_command); + if (nwords == 0) { + vi_error(); + return (-1); + } + if (count) { + int i; + + count--; + if (count >= nwords) { + vi_error(); + x_print_expansions(nwords, words, is_command); + x_free_words(nwords, words); + redraw_line(0); + return (-1); + } + /* + * Expand the count'th word to its basename + */ + if (is_command) { + match = words[count] + + x_basename(words[count], NULL); + /* If more than one possible match, use full path */ + for (i = 0; i < nwords; i++) + if (i != count && + strcmp(words[i] + x_basename(words[i], + NULL), match) == 0) { + match = words[count]; + break; + } + } else + match = words[count]; + match_len = strlen(match); + is_unique = true; + /* expanded = PRINT; next call undo */ + } else { + match = words[0]; + match_len = x_longest_prefix(nwords, words); + expanded = COMPLETE; /* next call will list completions */ + is_unique = nwords == 1; + } + + buf = save_edstate(es); + del_range(start, end); + es->cursor = start; + + /* escape all shell-sensitive characters and put the result into + * command buffer */ + rval = x_escape(match, match_len, x_vi_putbuf); + + if (rval == 0 && is_unique) { + /* If exact match, don't undo. Allows directory completions + * to be used (ie, complete the next portion of the path). + */ + expanded = NONE; + + /* If not a directory, add a space to the end... */ + if (match_len > 0 && match[match_len - 1] != '/') + rval = putbuf(" ", 1, 0); + } + x_free_words(nwords, words); + + modified = 1; + hnum = hlast; + insert = INSERT; + lastac = 0; /* prevent this from being redone... */ + refresh(0); + + return (rval); +} + +static int +print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED) +{ + int start, end, nwords; + char **words; + bool is_command; + + nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH, + est->cbuf, est->linelen, est->cursor, + &start, &end, &words, &is_command); + if (nwords == 0) { + vi_error(); + return (-1); + } + x_print_expansions(nwords, words, is_command); + x_free_words(nwords, words); + redraw_line(0); + return (0); +} + +/* Similar to x_zotc(emacs.c), but no tab weirdness */ +static void +x_vi_zotc(int c) +{ + if (c < ' ' || c == 0x7f) { + x_putc('^'); + c ^= '@'; + } + x_putc(c); +} + +static void +vi_error(void) +{ + /* Beem out of any macros as soon as an error occurs */ + vi_macro_reset(); + x_putc(7); + x_flush(); +} + +static void +vi_macro_reset(void) +{ + if (macro.p) { + afree(macro.buf, APERM); + memset((char *)¯o, 0, sizeof(macro)); + } +} +#endif /* !MKSH_S_NOVI */ diff --git a/mksh/src/emacsfn.h b/mksh/src/emacsfn.h new file mode 100644 index 0000000..1333399 --- /dev/null +++ b/mksh/src/emacsfn.h @@ -0,0 +1,89 @@ +#if defined(EMACSFN_DEFNS) +__RCSID("$MirOS: src/bin/mksh/emacsfn.h,v 1.5 2010/07/17 22:09:33 tg Exp $"); +#define FN(cname,sname,flags) static int x_##cname(int); +#elif defined(EMACSFN_ENUMS) +#define FN(cname,sname,flags) XFUNC_##cname, +#define F0(cname,sname,flags) XFUNC_##cname = 0, +#elif defined(EMACSFN_ITEMS) +#define FN(cname,sname,flags) { x_##cname, sname, flags }, +#endif + +#ifndef F0 +#define F0 FN +#endif + +F0(abort, "abort", 0) +FN(beg_hist, "beginning-of-history", 0) +FN(cls, "clear-screen", 0) +FN(comment, "comment", 0) +FN(comp_comm, "complete-command", 0) +FN(comp_file, "complete-file", 0) +FN(comp_list, "complete-list", 0) +FN(complete, "complete", 0) +FN(del_back, "delete-char-backward", XF_ARG) +FN(del_bword, "delete-word-backward", XF_ARG) +FN(del_char, "delete-char-forward", XF_ARG) +FN(del_fword, "delete-word-forward", XF_ARG) +FN(del_line, "kill-line", 0) +FN(draw_line, "redraw", 0) +#ifndef MKSH_SMALL +FN(edit_line, "edit-line", XF_ARG) +#endif +FN(end_hist, "end-of-history", 0) +FN(end_of_text, "eot", 0) +FN(enumerate, "list", 0) +FN(eot_del, "eot-or-delete", XF_ARG) +FN(error, "error", 0) +FN(expand, "expand-file", 0) +#ifndef MKSH_SMALL +FN(fold_capitalise, "capitalize-word", XF_ARG) +FN(fold_lower, "downcase-word", XF_ARG) +FN(fold_upper, "upcase-word", XF_ARG) +#endif +FN(goto_hist, "goto-history", XF_ARG) +#ifndef MKSH_SMALL +FN(ins_string, "macro-string", XF_NOBIND) +#endif +FN(insert, "auto-insert", XF_ARG) +FN(kill, "kill-to-eol", XF_ARG) +FN(kill_region, "kill-region", 0) +FN(list_comm, "list-command", 0) +FN(list_file, "list-file", 0) +FN(literal, "quote", 0) +FN(meta1, "prefix-1", XF_PREFIX) +FN(meta2, "prefix-2", XF_PREFIX) +FN(meta_yank, "yank-pop", 0) +FN(mv_back, "backward-char", XF_ARG) +FN(mv_begin, "beginning-of-line", 0) +FN(mv_bword, "backward-word", XF_ARG) +FN(mv_end, "end-of-line", 0) +FN(mv_forw, "forward-char", XF_ARG) +FN(mv_fword, "forward-word", XF_ARG) +FN(newline, "newline", 0) +FN(next_com, "down-history", XF_ARG) +FN(nl_next_com, "newline-and-next", 0) +FN(noop, "no-op", 0) +FN(prev_com, "up-history", XF_ARG) +FN(prev_histword, "prev-hist-word", XF_ARG) +FN(search_char_back, "search-character-backward", XF_ARG) +FN(search_char_forw, "search-character-forward", XF_ARG) +FN(search_hist, "search-history", 0) +#ifndef MKSH_SMALL +FN(search_hist_dn, "search-history-down", 0) +FN(search_hist_up, "search-history-up", 0) +#endif +FN(set_arg, "set-arg", XF_NOBIND) +FN(set_mark, "set-mark-command", 0) +FN(transpose, "transpose-chars", 0) +FN(version, "version", 0) +#ifndef MKSH_SMALL +FN(vt_hack, "vt100-hack", XF_ARG) +#endif +FN(xchg_point_mark, "exchange-point-and-mark", 0) +FN(yank, "yank", 0) + +#undef FN +#undef F0 +#undef EMACSFN_DEFNS +#undef EMACSFN_ENUMS +#undef EMACSFN_ITEMS diff --git a/mksh/src/eval.c b/mksh/src/eval.c new file mode 100644 index 0000000..c22e346 --- /dev/null +++ b/mksh/src/eval.c @@ -0,0 +1,1580 @@ +/* $OpenBSD: eval.c,v 1.35 2010/03/24 08:27:26 fgsch Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.90 2010/07/17 22:09:33 tg Exp $"); + +/* + * string expansion + * + * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution. + * second pass: alternation ({,}), filename expansion (*?[]). + */ + +/* expansion generator state */ +typedef struct Expand { + /* int type; */ /* see expand() */ + const char *str; /* string */ + union { + const char **strv; /* string[] */ + struct shf *shf; /* file */ + } u; /* source */ + struct tbl *var; /* variable in ${var..} */ + short split; /* split "$@" / call waitlast $() */ +} Expand; + +#define XBASE 0 /* scanning original */ +#define XSUB 1 /* expanding ${} string */ +#define XARGSEP 2 /* ifs0 between "$*" */ +#define XARG 3 /* expanding $*, $@ */ +#define XCOM 4 /* expanding $() */ +#define XNULLSUB 5 /* "$@" when $# is 0 (don't generate word) */ +#define XSUBMID 6 /* middle of expanding ${} */ + +/* States used for field splitting */ +#define IFS_WORD 0 /* word has chars (or quotes) */ +#define IFS_WS 1 /* have seen IFS white-space */ +#define IFS_NWS 2 /* have seen IFS non-white-space */ + +static int varsub(Expand *, const char *, const char *, int *, int *); +static int comsub(Expand *, const char *); +static char *trimsub(char *, char *, int); +static void glob(char *, XPtrV *, int); +static void globit(XString *, char **, char *, XPtrV *, int); +static const char *maybe_expand_tilde(const char *, XString *, char **, int); +static char *tilde(char *); +#ifndef MKSH_NOPWNAM +static char *homedir(char *); +#endif +static void alt_expand(XPtrV *, char *, char *, char *, int); +static size_t utflen(const char *); +static void utfincptr(const char *, mksh_ari_t *); + +/* UTFMODE functions */ +static size_t +utflen(const char *s) +{ + size_t n; + + if (UTFMODE) { + n = 0; + while (*s) { + s += utf_ptradj(s); + ++n; + } + } else + n = strlen(s); + return (n); +} + +static void +utfincptr(const char *s, mksh_ari_t *lp) +{ + const char *cp = s; + + while ((*lp)--) + cp += utf_ptradj(cp); + *lp = cp - s; +} + +/* compile and expand word */ +char * +substitute(const char *cp, int f) +{ + struct source *s, *sold; + + sold = source; + s = pushs(SWSTR, ATEMP); + s->start = s->str = cp; + source = s; + if (yylex(ONEWORD) != LWORD) + internal_errorf("substitute"); + source = sold; + afree(s, ATEMP); + return (evalstr(yylval.cp, f)); +} + +/* + * expand arg-list + */ +char ** +eval(const char **ap, int f) +{ + XPtrV w; + + if (*ap == NULL) { + union mksh_ccphack vap; + + vap.ro = ap; + return (vap.rw); + } + XPinit(w, 32); + XPput(w, NULL); /* space for shell name */ + while (*ap != NULL) + expand(*ap++, &w, f); + XPput(w, NULL); + return ((char **)XPclose(w) + 1); +} + +/* + * expand string + */ +char * +evalstr(const char *cp, int f) +{ + XPtrV w; + char *dp = null; + + XPinit(w, 1); + expand(cp, &w, f); + if (XPsize(w)) + dp = *XPptrv(w); + XPfree(w); + return (dp); +} + +/* + * expand string - return only one component + * used from iosetup to expand redirection files + */ +char * +evalonestr(const char *cp, int f) +{ + XPtrV w; + char *rv; + + XPinit(w, 1); + expand(cp, &w, f); + switch (XPsize(w)) { + case 0: + rv = null; + break; + case 1: + rv = (char *) *XPptrv(w); + break; + default: + rv = evalstr(cp, f&~DOGLOB); + break; + } + XPfree(w); + return (rv); +} + +/* for nested substitution: ${var:=$var2} */ +typedef struct SubType { + struct tbl *var; /* variable for ${var..} */ + struct SubType *prev; /* old type */ + struct SubType *next; /* poped type (to avoid re-allocating) */ + short stype; /* [=+-?%#] action after expanded word */ + short base; /* begin position of expanded word */ + short f; /* saved value of f (DOPAT, etc) */ + uint8_t quotep; /* saved value of quote (for ${..[%#]..}) */ + uint8_t quotew; /* saved value of quote (for ${..[+-=]..}) */ +} SubType; + +void +expand(const char *cp, /* input word */ + XPtrV *wp, /* output words */ + int f) /* DO* flags */ +{ + int c = 0; + int type; /* expansion type */ + int quote = 0; /* quoted */ + XString ds; /* destination string */ + char *dp; /* destination */ + const char *sp; /* source */ + int fdo, word; /* second pass flags; have word */ + int doblank; /* field splitting of parameter/command subst */ + Expand x = { /* expansion variables */ + NULL, { NULL }, NULL, 0 + }; + SubType st_head, *st; + int newlines = 0; /* For trailing newlines in COMSUB */ + int saw_eq, tilde_ok; + int make_magic; + size_t len; + + if (cp == NULL) + internal_errorf("expand(NULL)"); + /* for alias, readonly, set, typeset commands */ + if ((f & DOVACHECK) && is_wdvarassign(cp)) { + f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE); + f |= DOASNTILDE; + } + if (Flag(FNOGLOB)) + f &= ~DOGLOB; + if (Flag(FMARKDIRS)) + f |= DOMARKDIRS; + if (Flag(FBRACEEXPAND) && (f & DOGLOB)) + f |= DOBRACE_; + + Xinit(ds, dp, 128, ATEMP); /* init dest. string */ + type = XBASE; + sp = cp; + fdo = 0; + saw_eq = 0; + tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */ + doblank = 0; + make_magic = 0; + word = (f&DOBLANK) ? IFS_WS : IFS_WORD; + /* clang doesn't know OSUBST comes before CSUBST */ + memset(&st_head, 0, sizeof(st_head)); + st = &st_head; + + while (1) { + Xcheck(ds, dp); + + switch (type) { + case XBASE: /* original prefixed string */ + c = *sp++; + switch (c) { + case EOS: + c = 0; + break; + case CHAR: + c = *sp++; + break; + case QCHAR: + quote |= 2; /* temporary quote */ + c = *sp++; + break; + case OQUOTE: + word = IFS_WORD; + tilde_ok = 0; + quote = 1; + continue; + case CQUOTE: + quote = st->quotew; + continue; + case COMSUB: + tilde_ok = 0; + if (f & DONTRUNCOMMAND) { + word = IFS_WORD; + *dp++ = '$'; *dp++ = '('; + while (*sp != '\0') { + Xcheck(ds, dp); + *dp++ = *sp++; + } + *dp++ = ')'; + } else { + type = comsub(&x, sp); + if (type == XCOM && (f&DOBLANK)) + doblank++; + sp = strnul(sp) + 1; + newlines = 0; + } + continue; + case EXPRSUB: + word = IFS_WORD; + tilde_ok = 0; + if (f & DONTRUNCOMMAND) { + *dp++ = '$'; *dp++ = '('; *dp++ = '('; + while (*sp != '\0') { + Xcheck(ds, dp); + *dp++ = *sp++; + } + *dp++ = ')'; *dp++ = ')'; + } else { + struct tbl v; + char *p; + + v.flag = DEFINED|ISSET|INTEGER; + v.type = 10; /* not default */ + v.name[0] = '\0'; + v_evaluate(&v, substitute(sp, 0), + KSH_UNWIND_ERROR, true); + sp = strnul(sp) + 1; + for (p = str_val(&v); *p; ) { + Xcheck(ds, dp); + *dp++ = *p++; + } + } + continue; + case OSUBST: { /* ${{#}var{:}[=+-?#%]word} */ + /* format is: + * OSUBST [{x] plain-variable-part \0 + * compiled-word-part CSUBST [}x] + * This is where all syntax checking gets done... + */ + const char *varname = ++sp; /* skip the { or x (}) */ + int stype; + int slen = 0; + + sp = cstrchr(sp, '\0') + 1; /* skip variable */ + type = varsub(&x, varname, sp, &stype, &slen); + if (type < 0) { + char *beg, *end, *str; + + unwind_substsyn: + sp = varname - 2; /* restore sp */ + end = (beg = wdcopy(sp, ATEMP)) + + (wdscan(sp, CSUBST) - sp); + /* ({) the } or x is already skipped */ + if (end < wdscan(beg, EOS)) + *end = EOS; + str = snptreef(NULL, 64, "%S", beg); + afree(beg, ATEMP); + errorf("%s: bad substitution", str); + } + if (f & DOBLANK) + doblank++; + tilde_ok = 0; + if (type == XBASE) { /* expand? */ + if (!st->next) { + SubType *newst; + + newst = alloc(sizeof(SubType), ATEMP); + newst->next = NULL; + newst->prev = st; + st->next = newst; + } + st = st->next; + st->stype = stype; + st->base = Xsavepos(ds, dp); + st->f = f; + st->var = x.var; + st->quotew = st->quotep = quote; + /* skip qualifier(s) */ + if (stype) + sp += slen; + switch (stype & 0x7f) { + case '0': { + char *beg, *mid, *end, *stg; + mksh_ari_t from = 0, num = -1, flen, finc = 0; + + beg = wdcopy(sp, ATEMP); + mid = beg + (wdscan(sp, ADELIM) - sp); + stg = beg + (wdscan(sp, CSUBST) - sp); + if (mid >= stg) + goto unwind_substsyn; + mid[-2] = EOS; + if (mid[-1] == /*{*/'}') { + sp += mid - beg - 1; + end = NULL; + } else { + end = mid + + (wdscan(mid, ADELIM) - mid); + if (end >= stg) + goto unwind_substsyn; + end[-2] = EOS; + sp += end - beg - 1; + } + evaluate(substitute(stg = wdstrip(beg, false, false), 0), + &from, KSH_UNWIND_ERROR, true); + afree(stg, ATEMP); + if (end) { + evaluate(substitute(stg = wdstrip(mid, false, false), 0), + &num, KSH_UNWIND_ERROR, true); + afree(stg, ATEMP); + } + afree(beg, ATEMP); + beg = str_val(st->var); + flen = utflen(beg); + if (from < 0) { + if (-from < flen) + finc = flen + from; + } else + finc = from < flen ? from : flen; + if (UTFMODE) + utfincptr(beg, &finc); + beg += finc; + flen = utflen(beg); + if (num < 0 || num > flen) + num = flen; + if (UTFMODE) + utfincptr(beg, &num); + strndupx(x.str, beg, num, ATEMP); + goto do_CSUBST; + } + case '/': { + char *s, *p, *d, *sbeg, *end; + char *pat, *rrep; + char *tpat0, *tpat1, *tpat2; + + s = wdcopy(sp, ATEMP); + p = s + (wdscan(sp, ADELIM) - sp); + d = s + (wdscan(sp, CSUBST) - sp); + if (p >= d) + goto unwind_substsyn; + p[-2] = EOS; + if (p[-1] == /*{*/'}') + d = NULL; + else + d[-2] = EOS; + sp += (d ? d : p) - s - 1; + tpat0 = wdstrip(s, true, true); + pat = substitute(tpat0, 0); + if (d) { + d = wdstrip(p, true, false); + rrep = substitute(d, 0); + afree(d, ATEMP); + } else + rrep = null; + afree(s, ATEMP); + s = d = pat; + while (*s) + if (*s != '\\' || + s[1] == '%' || + s[1] == '#' || + s[1] == '\0' || + /* XXX really? */ s[1] == '\\' || + s[1] == '/') + *d++ = *s++; + else + s++; + *d = '\0'; + afree(tpat0, ATEMP); + + /* reject empty pattern */ + if (!*pat || gmatchx("", pat, false)) + goto no_repl; + + /* prepare string on which to work */ + strdupx(s, str_val(st->var), ATEMP); + sbeg = s; + + /* first see if we have any match at all */ + tpat0 = pat; + if (*pat == '#') { + /* anchor at the beginning */ + tpat1 = shf_smprintf("%s%c*", ++tpat0, MAGIC); + tpat2 = tpat1; + } else if (*pat == '%') { + /* anchor at the end */ + tpat1 = shf_smprintf("%c*%s", MAGIC, ++tpat0); + tpat2 = tpat0; + } else { + /* float */ + tpat1 = shf_smprintf("%c*%s%c*", MAGIC, pat, MAGIC); + tpat2 = tpat1 + 2; + } + again_repl: + /* this would not be necessary if gmatchx would return + * the start and end values of a match found, like re* + */ + if (!gmatchx(sbeg, tpat1, false)) + goto end_repl; + end = strnul(s); + /* now anchor the beginning of the match */ + if (*pat != '#') + while (sbeg <= end) { + if (gmatchx(sbeg, tpat2, false)) + break; + else + sbeg++; + } + /* now anchor the end of the match */ + p = end; + if (*pat != '%') + while (p >= sbeg) { + bool gotmatch; + + c = *p; *p = '\0'; + gotmatch = gmatchx(sbeg, tpat0, false); + *p = c; + if (gotmatch) + break; + p--; + } + strndupx(end, s, sbeg - s, ATEMP); + d = shf_smprintf("%s%s%s", end, rrep, p); + afree(end, ATEMP); + sbeg = d + (sbeg - s) + strlen(rrep); + afree(s, ATEMP); + s = d; + if (stype & 0x80) + goto again_repl; + end_repl: + afree(tpat1, ATEMP); + x.str = s; + no_repl: + afree(pat, ATEMP); + if (rrep != null) + afree(rrep, ATEMP); + goto do_CSUBST; + } + case '#': + case '%': + /* ! DOBLANK,DOBRACE_,DOTILDE */ + f = DOPAT | (f&DONTRUNCOMMAND) | + DOTEMP_; + st->quotew = quote = 0; + /* Prepend open pattern (so | + * in a trim will work as + * expected) + */ + *dp++ = MAGIC; + *dp++ = (char)('@' | 0x80); + break; + case '=': + /* Enabling tilde expansion + * after :s here is + * non-standard ksh, but is + * consistent with rules for + * other assignments. Not + * sure what POSIX thinks of + * this. + * Not doing tilde expansion + * for integer variables is a + * non-POSIX thing - makes + * sense though, since ~ is + * a arithmetic operator. + */ + if (!(x.var->flag & INTEGER)) + f |= DOASNTILDE|DOTILDE; + f |= DOTEMP_; + /* These will be done after the + * value has been assigned. + */ + f &= ~(DOBLANK|DOGLOB|DOBRACE_); + tilde_ok = 1; + break; + case '?': + f &= ~DOBLANK; + f |= DOTEMP_; + /* FALLTHROUGH */ + default: + /* Enable tilde expansion */ + tilde_ok = 1; + f |= DOTILDE; + } + } else + /* skip word */ + sp += wdscan(sp, CSUBST) - sp; + continue; + } + case CSUBST: /* only get here if expanding word */ + do_CSUBST: + sp++; /* ({) skip the } or x */ + tilde_ok = 0; /* in case of ${unset:-} */ + *dp = '\0'; + quote = st->quotep; + f = st->f; + if (f&DOBLANK) + doblank--; + switch (st->stype&0x7f) { + case '#': + case '%': + /* Append end-pattern */ + *dp++ = MAGIC; *dp++ = ')'; *dp = '\0'; + dp = Xrestpos(ds, dp, st->base); + /* Must use st->var since calling + * global would break things + * like x[i+=1]. + */ + x.str = trimsub(str_val(st->var), + dp, st->stype); + if (x.str[0] != '\0' || st->quotep) + type = XSUB; + else + type = XNULLSUB; + if (f&DOBLANK) + doblank++; + st = st->prev; + continue; + case '=': + /* Restore our position and substitute + * the value of st->var (may not be + * the assigned value in the presence + * of integer/right-adj/etc attributes). + */ + dp = Xrestpos(ds, dp, st->base); + /* Must use st->var since calling + * global would cause with things + * like x[i+=1] to be evaluated twice. + */ + /* Note: not exported by FEXPORT + * in AT&T ksh. + */ + /* XXX POSIX says readonly is only + * fatal for special builtins (setstr + * does readonly check). + */ + len = strlen(dp) + 1; + setstr(st->var, + debunk(alloc(len, ATEMP), + dp, len), KSH_UNWIND_ERROR); + x.str = str_val(st->var); + type = XSUB; + if (f&DOBLANK) + doblank++; + st = st->prev; + continue; + case '?': { + char *s = Xrestpos(ds, dp, st->base); + + errorf("%s: %s", st->var->name, + dp == s ? + "parameter null or not set" : + (debunk(s, s, strlen(s) + 1), s)); + } + case '0': + case '/': + dp = Xrestpos(ds, dp, st->base); + type = XSUB; + if (f&DOBLANK) + doblank++; + st = st->prev; + continue; + } + st = st->prev; + type = XBASE; + continue; + + case OPAT: /* open pattern: *(foo|bar) */ + /* Next char is the type of pattern */ + make_magic = 1; + c = *sp++ + 0x80; + break; + + case SPAT: /* pattern separator (|) */ + make_magic = 1; + c = '|'; + break; + + case CPAT: /* close pattern */ + make_magic = 1; + c = /*(*/ ')'; + break; + } + break; + + case XNULLSUB: + /* Special case for "$@" (and "${foo[@]}") - no + * word is generated if $# is 0 (unless there is + * other stuff inside the quotes). + */ + type = XBASE; + if (f&DOBLANK) { + doblank--; + /* not really correct: x=; "$x$@" should + * generate a null argument and + * set A; "${@:+}" shouldn't. + */ + if (dp == Xstring(ds, dp)) + word = IFS_WS; + } + continue; + + case XSUB: + case XSUBMID: + if ((c = *x.str++) == 0) { + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + break; + + case XARGSEP: + type = XARG; + quote = 1; + case XARG: + if ((c = *x.str++) == '\0') { + /* force null words to be created so + * set -- '' 2 ''; foo "$@" will do + * the right thing + */ + if (quote && x.split) + word = IFS_WORD; + if ((x.str = *x.u.strv++) == NULL) { + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + c = ifs0; + if (c == 0) { + if (quote && !x.split) + continue; + c = ' '; + } + if (quote && x.split) { + /* terminate word for "$@" */ + type = XARGSEP; + quote = 0; + } + } + break; + + case XCOM: + if (newlines) { /* Spit out saved NLs */ + c = '\n'; + --newlines; + } else { + while ((c = shf_getc(x.u.shf)) == 0 || c == '\n') + if (c == '\n') + /* Save newlines */ + newlines++; + if (newlines && c != EOF) { + shf_ungetc(c, x.u.shf); + c = '\n'; + --newlines; + } + } + if (c == EOF) { + newlines = 0; + shf_close(x.u.shf); + if (x.split) + subst_exstat = waitlast(); + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + break; + } + + /* check for end of word or IFS separation */ + if (c == 0 || (!quote && (f & DOBLANK) && doblank && + !make_magic && ctype(c, C_IFS))) { + /* How words are broken up: + * | value of c + * word | ws nws 0 + * ----------------------------------- + * IFS_WORD w/WS w/NWS w + * IFS_WS -/WS w/NWS - + * IFS_NWS -/NWS w/NWS w + * (w means generate a word) + * Note that IFS_NWS/0 generates a word (AT&T ksh + * doesn't do this, but POSIX does). + */ + if (word == IFS_WORD || + (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) { + char *p; + + *dp++ = '\0'; + p = Xclose(ds, dp); + if (fdo & DOBRACE_) + /* also does globbing */ + alt_expand(wp, p, p, + p + Xlength(ds, (dp - 1)), + fdo | (f & DOMARKDIRS)); + else if (fdo & DOGLOB) + glob(p, wp, f & DOMARKDIRS); + else if ((f & DOPAT) || !(fdo & DOMAGIC_)) + XPput(*wp, p); + else + XPput(*wp, debunk(p, p, strlen(p) + 1)); + fdo = 0; + saw_eq = 0; + tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; + if (c != 0) + Xinit(ds, dp, 128, ATEMP); + } + if (c == 0) + return; + if (word != IFS_NWS) + word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS; + } else { + if (type == XSUB) { + if (word == IFS_NWS && + Xlength(ds, dp) == 0) { + char *p; + + *(p = alloc(1, ATEMP)) = '\0'; + XPput(*wp, p); + } + type = XSUBMID; + } + + /* age tilde_ok info - ~ code tests second bit */ + tilde_ok <<= 1; + /* mark any special second pass chars */ + if (!quote) + switch (c) { + case '[': + case NOT: + case '-': + case ']': + /* For character classes - doesn't hurt + * to have magic !,-,]s outside of + * [...] expressions. + */ + if (f & (DOPAT | DOGLOB)) { + fdo |= DOMAGIC_; + if (c == '[') + fdo |= f & DOGLOB; + *dp++ = MAGIC; + } + break; + case '*': + case '?': + if (f & (DOPAT | DOGLOB)) { + fdo |= DOMAGIC_ | (f & DOGLOB); + *dp++ = MAGIC; + } + break; + case OBRACE: + case ',': + case CBRACE: + if ((f & DOBRACE_) && (c == OBRACE || + (fdo & DOBRACE_))) { + fdo |= DOBRACE_|DOMAGIC_; + *dp++ = MAGIC; + } + break; + case '=': + /* Note first unquoted = for ~ */ + if (!(f & DOTEMP_) && !saw_eq && + (Flag(FBRACEEXPAND) || + (f & DOASNTILDE))) { + saw_eq = 1; + tilde_ok = 1; + } + break; + case ':': /* : */ + /* Note unquoted : for ~ */ + if (!(f & DOTEMP_) && (f & DOASNTILDE)) + tilde_ok = 1; + break; + case '~': + /* tilde_ok is reset whenever + * any of ' " $( $(( ${ } are seen. + * Note that tilde_ok must be preserved + * through the sequence ${A=a=}~ + */ + if (type == XBASE && + (f & (DOTILDE|DOASNTILDE)) && + (tilde_ok & 2)) { + const char *p; + char *dp_x; + + dp_x = dp; + p = maybe_expand_tilde(sp, + &ds, &dp_x, + f & DOASNTILDE); + if (p) { + if (dp != dp_x) + word = IFS_WORD; + dp = dp_x; + sp = p; + continue; + } + } + break; + } + else + quote &= ~2; /* undo temporary */ + + if (make_magic) { + make_magic = 0; + fdo |= DOMAGIC_ | (f & DOGLOB); + *dp++ = MAGIC; + } else if (ISMAGIC(c)) { + fdo |= DOMAGIC_; + *dp++ = MAGIC; + } + *dp++ = c; /* save output char */ + word = IFS_WORD; + } + } +} + +/* + * Prepare to generate the string returned by ${} substitution. + */ +static int +varsub(Expand *xp, const char *sp, const char *word, + int *stypep, /* becomes qualifier type */ + int *slenp) /* " " len (=, :=, etc.) valid iff *stypep != 0 */ +{ + int c; + int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ + int stype; /* substitution type */ + int slen; + const char *p; + struct tbl *vp; + bool zero_ok = false; + + if ((stype = sp[0]) == '\0') /* Bad variable name */ + return (-1); + + xp->var = NULL; + + /*- + * ${#var}, string length (-U: characters, +U: octets) or array size + * ${%var}, string width (-U: screen columns, +U: octets) + */ + c = sp[1]; + if (stype == '%' && c == '\0') + return (-1); + if ((stype == '#' || stype == '%') && c != '\0') { + /* Can't have any modifiers for ${#...} or ${%...} */ + if (*word != CSUBST) + return (-1); + sp++; + /* Check for size of array */ + if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') && + p[2] == ']') { + int n = 0; + + if (stype != '#') + return (-1); + vp = global(arrayname(sp)); + if (vp->flag & (ISSET|ARRAY)) + zero_ok = true; + for (; vp; vp = vp->u.array) + if (vp->flag & ISSET) + n++; + c = n; + } else if (c == '*' || c == '@') { + if (stype != '#') + return (-1); + c = e->loc->argc; + } else { + p = str_val(global(sp)); + zero_ok = p != null; + if (stype == '#') + c = utflen(p); + else { + /* partial utf_mbswidth reimplementation */ + const char *s = p; + unsigned int wc; + size_t len; + int cw; + + c = 0; + while (*s) { + if (!UTFMODE || (len = utf_mbtowc(&wc, + s)) == (size_t)-1) + /* not UTFMODE or not UTF-8 */ + wc = (unsigned char)(*s++); + else + /* UTFMODE and UTF-8 */ + s += len; + /* wc == char or wchar at s++ */ + if ((cw = utf_wcwidth(wc)) == -1) { + /* 646, 8859-1, 10646 C0/C1 */ + c = -1; + break; + } + c += cw; + } + } + } + if (Flag(FNOUNSET) && c == 0 && !zero_ok) + errorf("%s: parameter not set", sp); + *stypep = 0; /* unqualified variable/string substitution */ + xp->str = shf_smprintf("%d", c); + return (XSUB); + } + + /* Check for qualifiers in word part */ + stype = 0; + c = word[slen = 0] == CHAR ? word[1] : 0; + if (c == ':') { + slen += 2; + stype = 0x80; + c = word[slen + 0] == CHAR ? word[slen + 1] : 0; + } + if (!stype && c == '/') { + slen += 2; + stype = c; + if (word[slen] == ADELIM) { + slen += 2; + stype |= 0x80; + } + } else if (stype == 0x80 && (c == ' ' || c == '0')) { + stype |= '0'; + } else if (ctype(c, C_SUBOP1)) { + slen += 2; + stype |= c; + } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */ + slen += 2; + stype = c; + if (word[slen + 0] == CHAR && c == word[slen + 1]) { + stype |= 0x80; + slen += 2; + } + } else if (stype) /* : is not ok */ + return (-1); + if (!stype && *word != CSUBST) + return (-1); + *stypep = stype; + *slenp = slen; + + c = sp[0]; + if (c == '*' || c == '@') { + switch (stype & 0x7f) { + case '=': /* can't assign to a vector */ + case '%': /* can't trim a vector (yet) */ + case '#': + case '0': + case '/': + return (-1); + } + if (e->loc->argc == 0) { + xp->str = null; + xp->var = global(sp); + state = c == '@' ? XNULLSUB : XSUB; + } else { + xp->u.strv = (const char **)e->loc->argv + 1; + xp->str = *xp->u.strv++; + xp->split = c == '@'; /* $@ */ + state = XARG; + } + zero_ok = true; /* POSIX 2009? */ + } else { + if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') && + p[2] == ']') { + XPtrV wv; + + switch (stype & 0x7f) { + case '=': /* can't assign to a vector */ + case '%': /* can't trim a vector (yet) */ + case '#': + case '?': + case '0': + case '/': + return (-1); + } + XPinit(wv, 32); + if ((c = sp[0]) == '!') + ++sp; + vp = global(arrayname(sp)); + for (; vp; vp = vp->u.array) { + if (!(vp->flag&ISSET)) + continue; + XPput(wv, c == '!' ? shf_smprintf("%lu", + arrayindex(vp)) : + str_val(vp)); + } + if (XPsize(wv) == 0) { + xp->str = null; + state = p[1] == '@' ? XNULLSUB : XSUB; + XPfree(wv); + } else { + XPput(wv, 0); + xp->u.strv = (const char **)XPptrv(wv); + xp->str = *xp->u.strv++; + xp->split = p[1] == '@'; /* ${foo[@]} */ + state = XARG; + } + } else { + /* Can't assign things like $! or $1 */ + if ((stype & 0x7f) == '=' && + ctype(*sp, C_VAR1 | C_DIGIT)) + return (-1); + if (*sp == '!' && sp[1]) { + ++sp; + xp->var = global(sp); + if (cstrchr(sp, '[')) { + if (xp->var->flag & ISSET) + xp->str = shf_smprintf("%lu", + arrayindex(xp->var)); + else + xp->str = null; + } else if (xp->var->flag & ISSET) + xp->str = xp->var->name; + else + xp->str = "0"; /* ksh93 compat */ + } else { + xp->var = global(sp); + xp->str = str_val(xp->var); + } + state = XSUB; + } + } + + c = stype&0x7f; + /* test the compiler's code generator */ + if (ctype(c, C_SUBOP2) || stype == (0x80 | '0') || c == '/' || + (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */ + c == '=' || c == '-' || c == '?' : c == '+')) + state = XBASE; /* expand word instead of variable value */ + if (Flag(FNOUNSET) && xp->str == null && !zero_ok && + (ctype(c, C_SUBOP2) || (state != XBASE && c != '+'))) + errorf("%s: parameter not set", sp); + return (state); +} + +/* + * Run the command in $(...) and read its output. + */ +static int +comsub(Expand *xp, const char *cp) +{ + Source *s, *sold; + struct op *t; + struct shf *shf; + + s = pushs(SSTRING, ATEMP); + s->start = s->str = cp; + sold = source; + t = compile(s); + afree(s, ATEMP); + source = sold; + + if (t == NULL) + return (XBASE); + + if (t != NULL && t->type == TCOM && /* $(<file) */ + *t->args == NULL && *t->vars == NULL && t->ioact != NULL) { + struct ioword *io = *t->ioact; + char *name; + + if ((io->flag&IOTYPE) != IOREAD) + errorf("funny $() command: %s", + snptreef(NULL, 32, "%R", io)); + shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0, + SHF_MAPHI|SHF_CLEXEC); + if (shf == NULL) + errorf("%s: cannot open $() input", name); + xp->split = 0; /* no waitlast() */ + } else { + int ofd1, pv[2]; + openpipe(pv); + shf = shf_fdopen(pv[0], SHF_RD, NULL); + ofd1 = savefd(1); + if (pv[1] != 1) { + ksh_dup2(pv[1], 1, false); + close(pv[1]); + } + execute(t, XFORK|XXCOM|XPIPEO, NULL); + restfd(1, ofd1); + startlast(); + xp->split = 1; /* waitlast() */ + } + + xp->u.shf = shf; + return (XCOM); +} + +/* + * perform #pattern and %pattern substitution in ${} + */ + +static char * +trimsub(char *str, char *pat, int how) +{ + char *end = strnul(str); + char *p, c; + + switch (how & 0xFF) { + case '#': /* shortest at beginning */ + for (p = str; p <= end; p += utf_ptradj(p)) { + c = *p; *p = '\0'; + if (gmatchx(str, pat, false)) { + *p = c; + return (p); + } + *p = c; + } + break; + case '#'|0x80: /* longest match at beginning */ + for (p = end; p >= str; p--) { + c = *p; *p = '\0'; + if (gmatchx(str, pat, false)) { + *p = c; + return (p); + } + *p = c; + } + break; + case '%': /* shortest match at end */ + p = end; + while (p >= str) { + if (gmatchx(p, pat, false)) + goto trimsub_match; + if (UTFMODE) { + char *op = p; + while ((p-- > str) && ((*p & 0xC0) == 0x80)) + ; + if ((p < str) || (p + utf_ptradj(p) != op)) + p = op - 1; + } else + --p; + } + break; + case '%'|0x80: /* longest match at end */ + for (p = str; p <= end; p++) + if (gmatchx(p, pat, false)) { + trimsub_match: + strndupx(end, str, p - str, ATEMP); + return (end); + } + break; + } + + return (str); /* no match, return string */ +} + +/* + * glob + * Name derived from V6's /etc/glob, the program that expanded filenames. + */ + +/* XXX cp not const 'cause slashes are temporarily replaced with NULs... */ +static void +glob(char *cp, XPtrV *wp, int markdirs) +{ + int oldsize = XPsize(*wp); + + if (glob_str(cp, wp, markdirs) == 0) + XPput(*wp, debunk(cp, cp, strlen(cp) + 1)); + else + qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize, + sizeof(void *), xstrcmp); +} + +#define GF_NONE 0 +#define GF_EXCHECK BIT(0) /* do existence check on file */ +#define GF_GLOBBED BIT(1) /* some globbing has been done */ +#define GF_MARKDIR BIT(2) /* add trailing / to directories */ + +/* Apply file globbing to cp and store the matching files in wp. Returns + * the number of matches found. + */ +int +glob_str(char *cp, XPtrV *wp, int markdirs) +{ + int oldsize = XPsize(*wp); + XString xs; + char *xp; + + Xinit(xs, xp, 256, ATEMP); + globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE); + Xfree(xs, xp); + + return (XPsize(*wp) - oldsize); +} + +static void +globit(XString *xs, /* dest string */ + char **xpp, /* ptr to dest end */ + char *sp, /* source path */ + XPtrV *wp, /* output list */ + int check) /* GF_* flags */ +{ + char *np; /* next source component */ + char *xp = *xpp; + char *se; + char odirsep; + + /* This to allow long expansions to be interrupted */ + intrcheck(); + + if (sp == NULL) { /* end of source path */ + /* We only need to check if the file exists if a pattern + * is followed by a non-pattern (eg, foo*x/bar; no check + * is needed for foo* since the match must exist) or if + * any patterns were expanded and the markdirs option is set. + * Symlinks make things a bit tricky... + */ + if ((check & GF_EXCHECK) || + ((check & GF_MARKDIR) && (check & GF_GLOBBED))) { +#define stat_check() (stat_done ? stat_done : \ + (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \ + ? -1 : 1)) + struct stat lstatb, statb; + int stat_done = 0; /* -1: failed, 1 ok */ + + if (lstat(Xstring(*xs, xp), &lstatb) < 0) + return; + /* special case for systems which strip trailing + * slashes from regular files (eg, /etc/passwd/). + * SunOS 4.1.3 does this... + */ + if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) && + xp[-1] == '/' && !S_ISDIR(lstatb.st_mode) && + (!S_ISLNK(lstatb.st_mode) || + stat_check() < 0 || !S_ISDIR(statb.st_mode))) + return; + /* Possibly tack on a trailing / if there isn't already + * one and if the file is a directory or a symlink to a + * directory + */ + if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) && + xp > Xstring(*xs, xp) && xp[-1] != '/' && + (S_ISDIR(lstatb.st_mode) || + (S_ISLNK(lstatb.st_mode) && stat_check() > 0 && + S_ISDIR(statb.st_mode)))) { + *xp++ = '/'; + *xp = '\0'; + } + } + strndupx(np, Xstring(*xs, xp), Xlength(*xs, xp), ATEMP); + XPput(*wp, np); + return; + } + + if (xp > Xstring(*xs, xp)) + *xp++ = '/'; + while (*sp == '/') { + Xcheck(*xs, xp); + *xp++ = *sp++; + } + np = strchr(sp, '/'); + if (np != NULL) { + se = np; + odirsep = *np; /* don't assume '/', can be multiple kinds */ + *np++ = '\0'; + } else { + odirsep = '\0'; /* keep gcc quiet */ + se = sp + strlen(sp); + } + + + /* Check if sp needs globbing - done to avoid pattern checks for strings + * containing MAGIC characters, open [s without the matching close ], + * etc. (otherwise opendir() will be called which may fail because the + * directory isn't readable - if no globbing is needed, only execute + * permission should be required (as per POSIX)). + */ + if (!has_globbing(sp, se)) { + XcheckN(*xs, xp, se - sp + 1); + debunk(xp, sp, Xnleft(*xs, xp)); + xp += strlen(xp); + *xpp = xp; + globit(xs, xpp, np, wp, check); + } else { + DIR *dirp; + struct dirent *d; + char *name; + int len; + int prefix_len; + + /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */ + *xp = '\0'; + prefix_len = Xlength(*xs, xp); + dirp = opendir(prefix_len ? Xstring(*xs, xp) : "."); + if (dirp == NULL) + goto Nodir; + while ((d = readdir(dirp)) != NULL) { + name = d->d_name; + if (name[0] == '.' && + (name[1] == 0 || (name[1] == '.' && name[2] == 0))) + continue; /* always ignore . and .. */ + if ((*name == '.' && *sp != '.') || + !gmatchx(name, sp, true)) + continue; + + len = strlen(d->d_name) + 1; + XcheckN(*xs, xp, len); + memcpy(xp, name, len); + *xpp = xp + len - 1; + globit(xs, xpp, np, wp, + (check & GF_MARKDIR) | GF_GLOBBED + | (np ? GF_EXCHECK : GF_NONE)); + xp = Xstring(*xs, xp) + prefix_len; + } + closedir(dirp); + Nodir: + ; + } + + if (np != NULL) + *--np = odirsep; +} + +/* remove MAGIC from string */ +char * +debunk(char *dp, const char *sp, size_t dlen) +{ + char *d; + const char *s; + + if ((s = cstrchr(sp, MAGIC))) { + if (s - sp >= (ssize_t)dlen) + return (dp); + memmove(dp, sp, s - sp); + for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++) + if (!ISMAGIC(*s) || !(*++s & 0x80) || + !vstrchr("*+?@! ", *s & 0x7f)) + *d++ = *s; + else { + /* extended pattern operators: *+?@! */ + if ((*s & 0x7f) != ' ') + *d++ = *s & 0x7f; + if (d - dp < (ssize_t)dlen) + *d++ = '('; + } + *d = '\0'; + } else if (dp != sp) + strlcpy(dp, sp, dlen); + return (dp); +} + +/* Check if p is an unquoted name, possibly followed by a / or :. If so + * puts the expanded version in *dcp,dp and returns a pointer in p just + * past the name, otherwise returns 0. + */ +static const char * +maybe_expand_tilde(const char *p, XString *dsp, char **dpp, int isassign) +{ + XString ts; + char *dp = *dpp; + char *tp; + const char *r; + + Xinit(ts, tp, 16, ATEMP); + /* : only for DOASNTILDE form */ + while (p[0] == CHAR && p[1] != '/' && (!isassign || p[1] != ':')) + { + Xcheck(ts, tp); + *tp++ = p[1]; + p += 2; + } + *tp = '\0'; + r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ? + tilde(Xstring(ts, tp)) : NULL; + Xfree(ts, tp); + if (r) { + while (*r) { + Xcheck(*dsp, dp); + if (ISMAGIC(*r)) + *dp++ = MAGIC; + *dp++ = *r++; + } + *dpp = dp; + r = p; + } + return (r); +} + +/* + * tilde expansion + * + * based on a version by Arnold Robbins + */ + +static char * +tilde(char *cp) +{ + char *dp = null; + + if (cp[0] == '\0') + dp = str_val(global("HOME")); + else if (cp[0] == '+' && cp[1] == '\0') + dp = str_val(global("PWD")); + else if (cp[0] == '-' && cp[1] == '\0') + dp = str_val(global("OLDPWD")); +#ifndef MKSH_NOPWNAM + else + dp = homedir(cp); +#endif + /* If HOME, PWD or OLDPWD are not set, don't expand ~ */ + return (dp == null ? NULL : dp); +} + +#ifndef MKSH_NOPWNAM +/* + * map userid to user's home directory. + * note that 4.3's getpw adds more than 6K to the shell, + * and the YP version probably adds much more. + * we might consider our own version of getpwnam() to keep the size down. + */ +static char * +homedir(char *name) +{ + struct tbl *ap; + + ap = ktenter(&homedirs, name, hash(name)); + if (!(ap->flag & ISSET)) { + struct passwd *pw; + + pw = getpwnam(name); + if (pw == NULL) + return (NULL); + strdupx(ap->val.s, pw->pw_dir, APERM); + ap->flag |= DEFINED|ISSET|ALLOC; + } + return (ap->val.s); +} +#endif + +static void +alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) +{ + int count = 0; + char *brace_start, *brace_end, *comma = NULL; + char *field_start; + char *p; + + /* search for open brace */ + for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2) + ; + brace_start = p; + + /* find matching close brace, if any */ + if (p) { + comma = NULL; + count = 1; + for (p += 2; *p && count; p++) { + if (ISMAGIC(*p)) { + if (*++p == OBRACE) + count++; + else if (*p == CBRACE) + --count; + else if (*p == ',' && count == 1) + comma = p; + } + } + } + /* no valid expansions... */ + if (!p || count != 0) { + /* Note that given a{{b,c} we do not expand anything (this is + * what AT&T ksh does. This may be changed to do the {b,c} + * expansion. } + */ + if (fdo & DOGLOB) + glob(start, wp, fdo & DOMARKDIRS); + else + XPput(*wp, debunk(start, start, end - start)); + return; + } + brace_end = p; + if (!comma) { + alt_expand(wp, start, brace_end, end, fdo); + return; + } + + /* expand expression */ + field_start = brace_start + 2; + count = 1; + for (p = brace_start + 2; p != brace_end; p++) { + if (ISMAGIC(*p)) { + if (*++p == OBRACE) + count++; + else if ((*p == CBRACE && --count == 0) || + (*p == ',' && count == 1)) { + char *news; + int l1, l2, l3; + + l1 = brace_start - start; + l2 = (p - 1) - field_start; + l3 = end - brace_end; + news = alloc(l1 + l2 + l3 + 1, ATEMP); + memcpy(news, start, l1); + memcpy(news + l1, field_start, l2); + memcpy(news + l1 + l2, brace_end, l3); + news[l1 + l2 + l3] = '\0'; + alt_expand(wp, news, news + l1, + news + l1 + l2 + l3, fdo); + field_start = p + 1; + } + } + } + return; +} diff --git a/mksh/src/exec.c b/mksh/src/exec.c new file mode 100644 index 0000000..391321a --- /dev/null +++ b/mksh/src/exec.c @@ -0,0 +1,1518 @@ +/* $OpenBSD: exec.c,v 1.49 2009/01/29 23:27:26 jaredy Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.75 2010/07/17 22:09:34 tg Exp $"); + +#ifndef MKSH_DEFAULT_EXECSHELL +#define MKSH_DEFAULT_EXECSHELL "/bin/sh" +#endif + +static int comexec(struct op *, struct tbl *volatile, const char **, + int volatile, volatile int *); +static void scriptexec(struct op *, const char **) MKSH_A_NORETURN; +static int call_builtin(struct tbl *, const char **); +static int iosetup(struct ioword *, struct tbl *); +static int herein(const char *, int); +static const char *do_selectargs(const char **, bool); +static Test_op dbteste_isa(Test_env *, Test_meta); +static const char *dbteste_getopnd(Test_env *, Test_op, bool); +static void dbteste_error(Test_env *, int, const char *); + +/* + * execute command tree + */ +int +execute(struct op *volatile t, + volatile int flags, /* if XEXEC don't fork */ + volatile int * volatile xerrok) +{ + int i; + volatile int rv = 0, dummy = 0; + int pv[2]; + const char ** volatile ap; + char ** volatile up; + const char *s, *cp; + struct ioword **iowp; + struct tbl *tp = NULL; + + if (t == NULL) + return (0); + + /* Caller doesn't care if XERROK should propagate. */ + if (xerrok == NULL) + xerrok = &dummy; + + if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE) + /* run in sub-process */ + return (exchild(t, flags & ~XTIME, xerrok, -1)); + + newenv(E_EXEC); + if (trap) + runtraps(0); + + if (t->type == TCOM) { + /* Clear subst_exstat before argument expansion. Used by + * null commands (see comexec() and c_eval()) and by c_set(). + */ + subst_exstat = 0; + + current_lineno = t->lineno; /* for $LINENO */ + + /* POSIX says expand command words first, then redirections, + * and assignments last.. + */ + up = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE); + if (flags & XTIME) + /* Allow option parsing (bizarre, but POSIX) */ + timex_hook(t, &up); + ap = (const char **)up; + if (Flag(FXTRACE) && ap[0]) { + shf_fprintf(shl_out, "%s", + substitute(str_val(global("PS4")), 0)); + for (i = 0; ap[i]; i++) + shf_fprintf(shl_out, "%s%c", ap[i], + ap[i + 1] ? ' ' : '\n'); + shf_flush(shl_out); + } + if (ap[0]) + tp = findcom(ap[0], FC_BI|FC_FUNC); + } + flags &= ~XTIME; + + if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) { + e->savefd = alloc(NUFILE * sizeof(short), ATEMP); + /* initialise to not redirected */ + memset(e->savefd, 0, NUFILE * sizeof(short)); + } + + /* do redirection, to be restored in quitenv() */ + if (t->ioact != NULL) + for (iowp = t->ioact; *iowp != NULL; iowp++) { + if (iosetup(*iowp, tp) < 0) { + exstat = rv = 1; + /* Redirection failures for special commands + * cause (non-interactive) shell to exit. + */ + if (tp && tp->type == CSHELL && + (tp->flag & SPEC_BI)) + errorfz(); + /* Deal with FERREXIT, quitenv(), etc. */ + goto Break; + } + } + + switch (t->type) { + case TCOM: + rv = comexec(t, tp, (const char **)ap, flags, xerrok); + break; + + case TPAREN: + rv = execute(t->left, flags | XFORK, xerrok); + break; + + case TPIPE: + flags |= XFORK; + flags &= ~XEXEC; + e->savefd[0] = savefd(0); + e->savefd[1] = savefd(1); + while (t->type == TPIPE) { + openpipe(pv); + ksh_dup2(pv[1], 1, false); /* stdout of curr */ + /** + * Let exchild() close pv[0] in child + * (if this isn't done, commands like + * (: ; cat /etc/termcap) | sleep 1 + * will hang forever). + */ + exchild(t->left, flags | XPIPEO | XCCLOSE, + NULL, pv[0]); + ksh_dup2(pv[0], 0, false); /* stdin of next */ + closepipe(pv); + flags |= XPIPEI; + t = t->right; + } + restfd(1, e->savefd[1]); /* stdout of last */ + e->savefd[1] = 0; /* no need to re-restore this */ + /* Let exchild() close 0 in parent, after fork, before wait */ + i = exchild(t, flags | XPCLOSE, xerrok, 0); + if (!(flags&XBGND) && !(flags&XXCOM)) + rv = i; + break; + + case TLIST: + while (t->type == TLIST) { + execute(t->left, flags & XERROK, NULL); + t = t->right; + } + rv = execute(t, flags & XERROK, xerrok); + break; + + case TCOPROC: { + sigset_t omask; + + /* Block sigchild as we are using things changed in the + * signal handler + */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + e->type = E_ERRH; + i = sigsetjmp(e->jbuf, 0); + if (i) { + sigprocmask(SIG_SETMASK, &omask, NULL); + quitenv(NULL); + unwind(i); + /* NOTREACHED */ + } + /* Already have a (live) co-process? */ + if (coproc.job && coproc.write >= 0) + errorf("coprocess already exists"); + + /* Can we re-use the existing co-process pipe? */ + coproc_cleanup(true); + + /* do this before opening pipes, in case these fail */ + e->savefd[0] = savefd(0); + e->savefd[1] = savefd(1); + + openpipe(pv); + if (pv[0] != 0) { + ksh_dup2(pv[0], 0, false); + close(pv[0]); + } + coproc.write = pv[1]; + coproc.job = NULL; + + if (coproc.readw >= 0) + ksh_dup2(coproc.readw, 1, false); + else { + openpipe(pv); + coproc.read = pv[0]; + ksh_dup2(pv[1], 1, false); + coproc.readw = pv[1]; /* closed before first read */ + coproc.njobs = 0; + /* create new coprocess id */ + ++coproc.id; + } + sigprocmask(SIG_SETMASK, &omask, NULL); + e->type = E_EXEC; /* no more need for error handler */ + + /* exchild() closes coproc.* in child after fork, + * will also increment coproc.njobs when the + * job is actually created. + */ + flags &= ~XEXEC; + exchild(t->left, flags | XBGND | XFORK | XCOPROC | XCCLOSE, + NULL, coproc.readw); + break; + } + + case TASYNC: + /* XXX non-optimal, I think - "(foo &)", forks for (), + * forks again for async... parent should optimise + * this to "foo &"... + */ + rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok); + break; + + case TOR: + case TAND: + rv = execute(t->left, XERROK, xerrok); + if ((rv == 0) == (t->type == TAND)) + rv = execute(t->right, XERROK, xerrok); + flags |= XERROK; + if (xerrok) + *xerrok = 1; + break; + + case TBANG: + rv = !execute(t->right, XERROK, xerrok); + flags |= XERROK; + if (xerrok) + *xerrok = 1; + break; + + case TDBRACKET: { + Test_env te; + + te.flags = TEF_DBRACKET; + te.pos.wp = t->args; + te.isa = dbteste_isa; + te.getopnd = dbteste_getopnd; + te.eval = test_eval; + te.error = dbteste_error; + + rv = test_parse(&te); + break; + } + + case TFOR: + case TSELECT: { + volatile bool is_first = true; + ap = (t->vars == NULL) ? e->loc->argv + 1 : + (const char **)eval((const char **)t->vars, + DOBLANK | DOGLOB | DOTILDE); + e->type = E_LOOP; + while (1) { + i = sigsetjmp(e->jbuf, 0); + if (!i) + break; + if ((e->flags&EF_BRKCONT_PASS) || + (i != LBREAK && i != LCONTIN)) { + quitenv(NULL); + unwind(i); + } else if (i == LBREAK) { + rv = 0; + goto Break; + } + } + rv = 0; /* in case of a continue */ + if (t->type == TFOR) { + while (*ap != NULL) { + setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); + rv = execute(t->left, flags & XERROK, xerrok); + } + } else { /* TSELECT */ + for (;;) { + if (!(cp = do_selectargs(ap, is_first))) { + rv = 1; + break; + } + is_first = false; + setstr(global(t->str), cp, KSH_UNWIND_ERROR); + execute(t->left, flags & XERROK, xerrok); + } + } + break; + } + + case TWHILE: + case TUNTIL: + e->type = E_LOOP; + while (1) { + i = sigsetjmp(e->jbuf, 0); + if (!i) + break; + if ((e->flags&EF_BRKCONT_PASS) || + (i != LBREAK && i != LCONTIN)) { + quitenv(NULL); + unwind(i); + } else if (i == LBREAK) { + rv = 0; + goto Break; + } + } + rv = 0; /* in case of a continue */ + while ((execute(t->left, XERROK, NULL) == 0) == + (t->type == TWHILE)) + rv = execute(t->right, flags & XERROK, xerrok); + break; + + case TIF: + case TELIF: + if (t->right == NULL) + break; /* should be error */ + rv = execute(t->left, XERROK, NULL) == 0 ? + execute(t->right->left, flags & XERROK, xerrok) : + execute(t->right->right, flags & XERROK, xerrok); + break; + + case TCASE: + cp = evalstr(t->str, DOTILDE); + for (t = t->left; t != NULL && t->type == TPAT; t = t->right) + for (ap = (const char **)t->vars; *ap; ap++) + if ((s = evalstr(*ap, DOTILDE|DOPAT)) && + gmatchx(cp, s, false)) + goto Found; + break; + Found: + rv = execute(t->left, flags & XERROK, xerrok); + break; + + case TBRACE: + rv = execute(t->left, flags & XERROK, xerrok); + break; + + case TFUNCT: + rv = define(t->str, t); + break; + + case TTIME: + /* Clear XEXEC so nested execute() call doesn't exit + * (allows "ls -l | time grep foo"). + */ + rv = timex(t, flags & ~XEXEC, xerrok); + break; + + case TEXEC: /* an eval'd TCOM */ + s = t->args[0]; + up = makenv(); + restoresigs(); + cleanup_proc_env(); + { + union mksh_ccphack cargs; + + cargs.ro = t->args; + execve(t->str, cargs.rw, up); + rv = errno; + } + if (rv == ENOEXEC) + scriptexec(t, (const char **)up); + else + errorf("%s: %s", s, strerror(rv)); + } + Break: + exstat = rv; + + quitenv(NULL); /* restores IO */ + if ((flags&XEXEC)) + unwind(LEXIT); /* exit child */ + if (rv != 0 && !(flags & XERROK) && + (xerrok == NULL || !*xerrok)) { + trapsig(SIGERR_); + if (Flag(FERREXIT)) + unwind(LERROR); + } + return (rv); +} + +/* + * execute simple command + */ + +static int +comexec(struct op *t, struct tbl *volatile tp, const char **ap, + volatile int flags, volatile int *xerrok) +{ + int i; + volatile int rv = 0; + const char *cp; + const char **lastp; + static struct op texec; /* Must be static (XXX but why?) */ + int type_flags; + int keepasn_ok; + int fcflags = FC_BI|FC_FUNC|FC_PATH; + bool bourne_function_call = false; + struct block *l_expand, *l_assign; + + /* snag the last argument for $_ XXX not the same as AT&T ksh, + * which only seems to set $_ after a newline (but not in + * functions/dot scripts, but in interactive and script) - + * perhaps save last arg here and set it in shell()?. + */ + if (Flag(FTALKING) && *(lastp = ap)) { + while (*++lastp) + ; + /* setstr() can't fail here */ + setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp, + KSH_RETURN_ERROR); + } + + /* Deal with the shell builtins builtin, exec and command since + * they can be followed by other commands. This must be done before + * we know if we should create a local block which must be done + * before we can do a path search (in case the assignments change + * PATH). + * Odd cases: + * FOO=bar exec >/dev/null FOO is kept but not exported + * FOO=bar exec foobar FOO is exported + * FOO=bar command exec >/dev/null FOO is neither kept nor exported + * FOO=bar command FOO is neither kept nor exported + * PATH=... foobar use new PATH in foobar search + */ + keepasn_ok = 1; + while (tp && tp->type == CSHELL) { + fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */ + if (tp->val.f == c_builtin) { + if ((cp = *++ap) == NULL) { + tp = NULL; + break; + } + tp = findcom(cp, FC_BI); + if (tp == NULL) + errorf("builtin: %s: not a builtin", cp); + continue; + } else if (tp->val.f == c_exec) { + if (ap[1] == NULL) + break; + ap++; + flags |= XEXEC; + } else if (tp->val.f == c_command) { + int optc, saw_p = 0; + + /* Ugly dealing with options in two places (here and + * in c_command(), but such is life) + */ + ksh_getopt_reset(&builtin_opt, 0); + while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p') + saw_p = 1; + if (optc != EOF) + break; /* command -vV or something */ + /* don't look for functions */ + fcflags = FC_BI|FC_PATH; + if (saw_p) { + if (Flag(FRESTRICTED)) { + warningf(true, + "command -p: restricted"); + rv = 1; + goto Leave; + } + fcflags |= FC_DEFPATH; + } + ap += builtin_opt.optind; + /* POSIX says special builtins lose their status + * if accessed using command. + */ + keepasn_ok = 0; + if (!ap[0]) { + /* ensure command with no args exits with 0 */ + subst_exstat = 0; + break; + } + } else + break; + tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC)); + } + l_expand = e->loc; + if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN)))) + type_flags = 0; + else { + /* create new variable/function block */ + newblock(); + /* ksh functions don't keep assignments, POSIX functions do. */ + if (keepasn_ok && tp && tp->type == CFUNC && + !(tp->flag & FKSH)) { + bourne_function_call = true; + type_flags = EXPORT; + } else + type_flags = LOCAL|LOCAL_COPY|EXPORT; + } + l_assign = e->loc; + if (Flag(FEXPORT)) + type_flags |= EXPORT; + for (i = 0; t->vars[i]; i++) { + /* do NOT lookup in the new var/fn block just created */ + e->loc = l_expand; + cp = evalstr(t->vars[i], DOASNTILDE); + e->loc = l_assign; + /* but assign in there as usual */ + + if (Flag(FXTRACE)) { + if (i == 0) + shf_fprintf(shl_out, "%s", + substitute(str_val(global("PS4")), 0)); + shf_fprintf(shl_out, "%s%c", cp, + t->vars[i + 1] ? ' ' : '\n'); + if (!t->vars[i + 1]) + shf_flush(shl_out); + } + typeset(cp, type_flags, 0, 0, 0); + if (bourne_function_call && !(type_flags & EXPORT)) + typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0); + } + + if ((cp = *ap) == NULL) { + rv = subst_exstat; + goto Leave; + } else if (!tp) { + if (Flag(FRESTRICTED) && vstrchr(cp, '/')) { + warningf(true, "%s: restricted", cp); + rv = 1; + goto Leave; + } + tp = findcom(cp, fcflags); + } + + switch (tp->type) { + case CSHELL: /* shell built-in */ + rv = call_builtin(tp, (const char **)ap); + break; + + case CFUNC: { /* function call */ + volatile unsigned char old_xflag; + volatile Tflag old_inuse; + const char *volatile old_kshname; + + if (!(tp->flag & ISSET)) { + struct tbl *ftp; + + if (!tp->u.fpath) { + if (tp->u2.errno_) { + warningf(true, + "%s: can't find function " + "definition file - %s", + cp, strerror(tp->u2.errno_)); + rv = 126; + } else { + warningf(true, + "%s: can't find function " + "definition file", cp); + rv = 127; + } + break; + } + if (include(tp->u.fpath, 0, NULL, 0) < 0) { + rv = errno; + warningf(true, + "%s: can't open function definition file %s - %s", + cp, tp->u.fpath, strerror(rv)); + rv = 127; + break; + } + if (!(ftp = findfunc(cp, hash(cp), false)) || + !(ftp->flag & ISSET)) { + warningf(true, + "%s: function not defined by %s", + cp, tp->u.fpath); + rv = 127; + break; + } + tp = ftp; + } + + /* ksh functions set $0 to function name, POSIX functions leave + * $0 unchanged. + */ + old_kshname = kshname; + if (tp->flag & FKSH) + kshname = ap[0]; + else + ap[0] = kshname; + e->loc->argv = ap; + for (i = 0; *ap++ != NULL; i++) + ; + e->loc->argc = i - 1; + /* ksh-style functions handle getopts sanely, + * Bourne/POSIX functions are insane... + */ + if (tp->flag & FKSH) { + e->loc->flags |= BF_DOGETOPTS; + e->loc->getopts_state = user_opt; + getopts_reset(1); + } + + old_xflag = Flag(FXTRACE); + Flag(FXTRACE) = tp->flag & TRACE ? 1 : 0; + + old_inuse = tp->flag & FINUSE; + tp->flag |= FINUSE; + + e->type = E_FUNC; + i = sigsetjmp(e->jbuf, 0); + if (i == 0) { + /* seems odd to pass XERROK here, but AT&T ksh does */ + exstat = execute(tp->val.t, flags & XERROK, xerrok); + i = LRETURN; + } + kshname = old_kshname; + Flag(FXTRACE) = old_xflag; + tp->flag = (tp->flag & ~FINUSE) | old_inuse; + /* Were we deleted while executing? If so, free the execution + * tree. todo: Unfortunately, the table entry is never re-used + * until the lookup table is expanded. + */ + if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) { + if (tp->flag & ALLOC) { + tp->flag &= ~ALLOC; + tfree(tp->val.t, tp->areap); + } + tp->flag = 0; + } + switch (i) { + case LRETURN: + case LERROR: + rv = exstat; + break; + case LINTR: + case LEXIT: + case LLEAVE: + case LSHELL: + quitenv(NULL); + unwind(i); + /* NOTREACHED */ + default: + quitenv(NULL); + internal_errorf("CFUNC %d", i); + } + break; + } + + case CEXEC: /* executable command */ + case CTALIAS: /* tracked alias */ + if (!(tp->flag&ISSET)) { + /* errno_ will be set if the named command was found + * but could not be executed (permissions, no execute + * bit, directory, etc). Print out a (hopefully) + * useful error message and set the exit status to 126. + */ + if (tp->u2.errno_) { + warningf(true, "%s: cannot execute - %s", cp, + strerror(tp->u2.errno_)); + rv = 126; /* POSIX */ + } else { + warningf(true, "%s: not found", cp); + rv = 127; + } + break; + } + + /* set $_ to programme's full path */ + /* setstr() can't fail here */ + setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0), + tp->val.s, KSH_RETURN_ERROR); + + if (flags&XEXEC) { + j_exit(); + if (!(flags&XBGND) +#ifndef MKSH_UNEMPLOYED + || Flag(FMONITOR) +#endif + ) { + setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG); + setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG); + } + } + + /* to fork we set up a TEXEC node and call execute */ + texec.type = TEXEC; + texec.left = t; /* for tprint */ + texec.str = tp->val.s; + texec.args = ap; + rv = exchild(&texec, flags, xerrok, -1); + break; + } + Leave: + if (flags & XEXEC) { + exstat = rv; + unwind(LLEAVE); + } + return (rv); +} + +static void +scriptexec(struct op *tp, const char **ap) +{ + const char *sh; +#ifndef MKSH_SMALL + unsigned char *cp; + char buf[64]; /* 64 == MAXINTERP in MirBSD <sys/param.h> */ + int fd; +#endif + union mksh_ccphack args, cap; + + sh = str_val(global("EXECSHELL")); + if (sh && *sh) + sh = search(sh, path, X_OK, NULL); + if (!sh || !*sh) + sh = MKSH_DEFAULT_EXECSHELL; + + *tp->args-- = tp->str; + +#ifndef MKSH_SMALL + if ((fd = open(tp->str, O_RDONLY)) >= 0) { + /* read first MAXINTERP octets from file */ + if (read(fd, buf, sizeof(buf)) <= 0) + /* read error -> no good */ + buf[0] = '\0'; + close(fd); + /* scan for newline (or CR) or NUL _before_ end of buffer */ + cp = (unsigned char *)buf; + while ((char *)cp < (buf + sizeof(buf))) + if (*cp == '\0' || *cp == '\n' || *cp == '\r') { + *cp = '\0'; + break; + } else + ++cp; + /* if the shebang line is longer than MAXINTERP, bail out */ + if ((char *)cp >= (buf + sizeof(buf))) + goto noshebang; + /* skip UTF-8 Byte Order Mark, if present */ + cp = (unsigned char *)buf; + if ((cp[0] == 0xEF) && (cp[1] == 0xBB) && (cp[2] == 0xBF)) + cp += 3; + /* bail out if read error (above) or no shebang */ + if ((cp[0] != '#') || (cp[1] != '!')) + goto noshebang; + cp += 2; + /* skip whitespace before shell name */ + while (*cp == ' ' || *cp == '\t') + ++cp; + /* just whitespace on the line? */ + if (*cp == '\0') + goto noshebang; + /* no, we actually found an interpreter name */ + sh = (char *)cp; + /* look for end of shell/interpreter name */ + while (*cp != ' ' && *cp != '\t' && *cp != '\0') + ++cp; + /* any arguments? */ + if (*cp) { + *cp++ = '\0'; + /* skip spaces before arguments */ + while (*cp == ' ' || *cp == '\t') + ++cp; + /* pass it all in ONE argument (historic reasons) */ + if (*cp) + *tp->args-- = (char *)cp; + } + noshebang: + fd = buf[0] << 8 | buf[1]; + if ((fd == /* OMAGIC */ 0407) || + (fd == /* NMAGIC */ 0410) || + (fd == /* ZMAGIC */ 0413) || + (fd == /* QMAGIC */ 0314) || + (fd == /* ECOFF_I386 */ 0x4C01) || + (fd == /* ECOFF_M68K */ 0x0150 || fd == 0x5001) || + (fd == /* ECOFF_SH */ 0x0500 || fd == 0x0005) || + (fd == 0x7F45 && buf[2] == 'L' && buf[3] == 'F') || + (fd == /* "MZ" */ 0x4D5A) || + (fd == /* gzip */ 0x1F8B)) + errorf("%s: not executable: magic %04X", tp->str, fd); + } +#endif + args.ro = tp->args; + *args.ro = sh; + + cap.ro = ap; + execve(args.rw[0], args.rw, cap.rw); + + /* report both the programme that was run and the bogus interpreter */ + errorf("%s: %s: %s", tp->str, sh, strerror(errno)); +} + +int +shcomexec(const char **wp) +{ + struct tbl *tp; + + tp = ktsearch(&builtins, *wp, hash(*wp)); + if (tp == NULL) + internal_errorf("shcomexec: %s", *wp); + return (call_builtin(tp, wp)); +} + +/* + * Search function tables for a function. If create set, a table entry + * is created if none is found. + */ +struct tbl * +findfunc(const char *name, uint32_t h, bool create) +{ + struct block *l; + struct tbl *tp = NULL; + + for (l = e->loc; l; l = l->next) { + tp = ktsearch(&l->funs, name, h); + if (tp) + break; + if (!l->next && create) { + tp = ktenter(&l->funs, name, h); + tp->flag = DEFINED; + tp->type = CFUNC; + tp->val.t = NULL; + break; + } + } + return (tp); +} + +/* + * define function. Returns 1 if function is being undefined (t == 0) and + * function did not exist, returns 0 otherwise. + */ +int +define(const char *name, struct op *t) +{ + struct tbl *tp; + bool was_set = false; + + while (1) { + tp = findfunc(name, hash(name), true); + + if (tp->flag & ISSET) + was_set = true; + /* If this function is currently being executed, we zap this + * table entry so findfunc() won't see it + */ + if (tp->flag & FINUSE) { + tp->name[0] = '\0'; + tp->flag &= ~DEFINED; /* ensure it won't be found */ + tp->flag |= FDELETE; + } else + break; + } + + if (tp->flag & ALLOC) { + tp->flag &= ~(ISSET|ALLOC); + tfree(tp->val.t, tp->areap); + } + + if (t == NULL) { /* undefine */ + ktdelete(tp); + return (was_set ? 0 : 1); + } + + tp->val.t = tcopy(t->left, tp->areap); + tp->flag |= (ISSET|ALLOC); + if (t->u.ksh_func) + tp->flag |= FKSH; + + return (0); +} + +/* + * add builtin + */ +void +builtin(const char *name, int (*func) (const char **)) +{ + struct tbl *tp; + Tflag flag; + + /* see if any flags should be set for this builtin */ + for (flag = 0; ; name++) { + if (*name == '=') /* command does variable assignment */ + flag |= KEEPASN; + else if (*name == '*') /* POSIX special builtin */ + flag |= SPEC_BI; + else if (*name == '+') /* POSIX regular builtin */ + flag |= REG_BI; + else + break; + } + + tp = ktenter(&builtins, name, hash(name)); + tp->flag = DEFINED | flag; + tp->type = CSHELL; + tp->val.f = func; +} + +/* + * find command + * either function, hashed command, or built-in (in that order) + */ +struct tbl * +findcom(const char *name, int flags) +{ + static struct tbl temp; + uint32_t h = hash(name); + struct tbl *tp = NULL, *tbi; + unsigned char insert = Flag(FTRACKALL); /* insert if not found */ + char *fpath; /* for function autoloading */ + union mksh_cchack npath; + + if (vstrchr(name, '/')) { + insert = 0; + /* prevent FPATH search below */ + flags &= ~FC_FUNC; + goto Search; + } + tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL; + /* POSIX says special builtins first, then functions, then + * POSIX regular builtins, then search path... + */ + if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI)) + tp = tbi; + if (!tp && (flags & FC_FUNC)) { + tp = findfunc(name, h, false); + if (tp && !(tp->flag & ISSET)) { + if ((fpath = str_val(global("FPATH"))) == null) { + tp->u.fpath = NULL; + tp->u2.errno_ = 0; + } else + tp->u.fpath = search(name, fpath, R_OK, + &tp->u2.errno_); + } + } + if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI)) + tp = tbi; + if (!tp && (flags & FC_UNREGBI) && tbi) + tp = tbi; + if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) { + tp = ktsearch(&taliases, name, h); + if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) { + if (tp->flag & ALLOC) { + tp->flag &= ~ALLOC; + afree(tp->val.s, APERM); + } + tp->flag &= ~ISSET; + } + } + + Search: + if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) && + (flags & FC_PATH)) { + if (!tp) { + if (insert && !(flags & FC_DEFPATH)) { + tp = ktenter(&taliases, name, h); + tp->type = CTALIAS; + } else { + tp = &temp; + tp->type = CEXEC; + } + tp->flag = DEFINED; /* make ~ISSET */ + } + npath.ro = search(name, flags & FC_DEFPATH ? def_path : path, + X_OK, &tp->u2.errno_); + if (npath.ro) { + strdupx(tp->val.s, npath.ro, APERM); + if (npath.ro != name) + afree(npath.rw, ATEMP); + tp->flag |= ISSET|ALLOC; + } else if ((flags & FC_FUNC) && + (fpath = str_val(global("FPATH"))) != null && + (npath.ro = search(name, fpath, R_OK, + &tp->u2.errno_)) != NULL) { + /* An undocumented feature of AT&T ksh is that it + * searches FPATH if a command is not found, even + * if the command hasn't been set up as an autoloaded + * function (ie, no typeset -uf). + */ + tp = &temp; + tp->type = CFUNC; + tp->flag = DEFINED; /* make ~ISSET */ + tp->u.fpath = npath.ro; + } + } + return (tp); +} + +/* + * flush executable commands with relative paths + */ +void +flushcom(int all) /* just relative or all */ +{ + struct tbl *tp; + struct tstate ts; + + for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; ) + if ((tp->flag&ISSET) && (all || tp->val.s[0] != '/')) { + if (tp->flag&ALLOC) { + tp->flag &= ~(ALLOC|ISSET); + afree(tp->val.s, APERM); + } + tp->flag &= ~ISSET; + } +} + +/* Check if path is something we want to find. Returns -1 for failure. */ +int +search_access(const char *lpath, int mode, + int *errnop) /* set if candidate found, but not suitable */ +{ + int ret, err = 0; + struct stat statb; + + if (stat(lpath, &statb) < 0) + return (-1); + ret = access(lpath, mode); + if (ret < 0) + err = errno; /* File exists, but we can't access it */ + else if (mode == X_OK && (!S_ISREG(statb.st_mode) || + !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + /* This 'cause access() says root can execute everything */ + ret = -1; + err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; + } + if (err && errnop && !*errnop) + *errnop = err; + return (ret); +} + +/* + * search for command with PATH + */ +const char * +search(const char *name, const char *lpath, + int mode, /* R_OK or X_OK */ + int *errnop) /* set if candidate found, but not suitable */ +{ + const char *sp, *p; + char *xp; + XString xs; + int namelen; + + if (errnop) + *errnop = 0; + if (vstrchr(name, '/')) { + if (search_access(name, mode, errnop) == 0) + return (name); + return (NULL); + } + + namelen = strlen(name) + 1; + Xinit(xs, xp, 128, ATEMP); + + sp = lpath; + while (sp != NULL) { + xp = Xstring(xs, xp); + if (!(p = cstrchr(sp, ':'))) + p = sp + strlen(sp); + if (p != sp) { + XcheckN(xs, xp, p - sp); + memcpy(xp, sp, p - sp); + xp += p - sp; + *xp++ = '/'; + } + sp = p; + XcheckN(xs, xp, namelen); + memcpy(xp, name, namelen); + if (search_access(Xstring(xs, xp), mode, errnop) == 0) + return (Xclose(xs, xp + namelen)); + if (*sp++ == '\0') + sp = NULL; + } + Xfree(xs, xp); + return (NULL); +} + +static int +call_builtin(struct tbl *tp, const char **wp) +{ + int rv; + + builtin_argv0 = wp[0]; + builtin_flag = tp->flag; + shf_reopen(1, SHF_WR, shl_stdout); + shl_stdout_ok = 1; + ksh_getopt_reset(&builtin_opt, GF_ERROR); + rv = (*tp->val.f)(wp); + shf_flush(shl_stdout); + shl_stdout_ok = 0; + builtin_flag = 0; + builtin_argv0 = NULL; + return (rv); +} + +/* + * set up redirection, saving old fds in e->savefd + */ +static int +iosetup(struct ioword *iop, struct tbl *tp) +{ + int u = -1; + char *cp = iop->name; + int iotype = iop->flag & IOTYPE; + int do_open = 1, do_close = 0, flags = 0; + struct ioword iotmp; + struct stat statb; + + if (iotype != IOHERE) + cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); + + /* Used for tracing and error messages to print expanded cp */ + iotmp = *iop; + iotmp.name = (iotype == IOHERE) ? NULL : cp; + iotmp.flag |= IONAMEXP; + + if (Flag(FXTRACE)) + shellf("%s%s\n", + substitute(str_val(global("PS4")), 0), + snptreef(NULL, 32, "%R", &iotmp)); + + switch (iotype) { + case IOREAD: + flags = O_RDONLY; + break; + + case IOCAT: + flags = O_WRONLY | O_APPEND | O_CREAT; + break; + + case IOWRITE: + flags = O_WRONLY | O_CREAT | O_TRUNC; + /* The stat() is here to allow redirections to + * things like /dev/null without error. + */ + if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) && + (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode))) + flags |= O_EXCL; + break; + + case IORDWR: + flags = O_RDWR | O_CREAT; + break; + + case IOHERE: + do_open = 0; + /* herein() returns -2 if error has been printed */ + u = herein(iop->heredoc, iop->flag & IOEVAL); + /* cp may have wrong name */ + break; + + case IODUP: { + const char *emsg; + + do_open = 0; + if (*cp == '-' && !cp[1]) { + u = 1009; /* prevent error return below */ + do_close = 1; + } else if ((u = check_fd(cp, + X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK), + &emsg)) < 0) { + warningf(true, "%s: %s", + snptreef(NULL, 32, "%R", &iotmp), emsg); + return (-1); + } + if (u == iop->unit) + return (0); /* "dup from" == "dup to" */ + break; + } + } + + if (do_open) { + if (Flag(FRESTRICTED) && (flags & O_CREAT)) { + warningf(true, "%s: restricted", cp); + return (-1); + } + u = open(cp, flags, 0666); + } + if (u < 0) { + /* herein() may already have printed message */ + if (u == -1) { + u = errno; + warningf(true, "cannot %s %s: %s", + iotype == IODUP ? "dup" : + (iotype == IOREAD || iotype == IOHERE) ? + "open" : "create", cp, strerror(u)); + } + return (-1); + } + /* Do not save if it has already been redirected (i.e. "cat >x >y"). */ + if (e->savefd[iop->unit] == 0) { + /* If these are the same, it means unit was previously closed */ + if (u == iop->unit) + e->savefd[iop->unit] = -1; + else + /* c_exec() assumes e->savefd[fd] set for any + * redirections. Ask savefd() not to close iop->unit; + * this allows error messages to be seen if iop->unit + * is 2; also means we can't lose the fd (eg, both + * dup2 below and dup2 in restfd() failing). + */ + e->savefd[iop->unit] = savefd(iop->unit); + } + + if (do_close) + close(iop->unit); + else if (u != iop->unit) { + if (ksh_dup2(u, iop->unit, true) < 0) { + int ev; + + ev = errno; + warningf(true, + "could not finish (dup) redirection %s: %s", + snptreef(NULL, 32, "%R", &iotmp), + strerror(ev)); + if (iotype != IODUP) + close(u); + return (-1); + } + if (iotype != IODUP) + close(u); + /* Touching any co-process fd in an empty exec + * causes the shell to close its copies + */ + else if (tp && tp->type == CSHELL && tp->val.f == c_exec) { + if (iop->flag & IORDUP) /* possible exec <&p */ + coproc_read_close(u); + else /* possible exec >&p */ + coproc_write_close(u); + } + } + if (u == 2) /* Clear any write errors */ + shf_reopen(2, SHF_WR, shl_out); + return (0); +} + +/* + * open here document temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int +herein(const char *content, int sub) +{ + volatile int fd = -1; + struct source *s, *volatile osource; + struct shf *volatile shf; + struct temp *h; + int i; + + /* ksh -c 'cat << EOF' can cause this... */ + if (content == NULL) { + warningf(true, "here document missing"); + return (-2); /* special to iosetup(): don't print error */ + } + + /* Create temp file to hold content (done before newenv so temp + * doesn't get removed too soon). + */ + h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps); + if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) { + fd = errno; + warningf(true, "can't %s temporary file %s: %s", + !shf ? "create" : "open", + h->name, strerror(fd)); + if (shf) + shf_close(shf); + return (-2 /* special to iosetup(): don't print error */); + } + + osource = source; + newenv(E_ERRH); + i = sigsetjmp(e->jbuf, 0); + if (i) { + source = osource; + quitenv(shf); + close(fd); + return (-2); /* special to iosetup(): don't print error */ + } + if (sub) { + /* Do substitutions on the content of heredoc */ + s = pushs(SSTRING, ATEMP); + s->start = s->str = content; + source = s; + if (yylex(ONEWORD|HEREDOC) != LWORD) + internal_errorf("herein: yylex"); + source = osource; + shf_puts(evalstr(yylval.cp, 0), shf); + } else + shf_puts(content, shf); + + quitenv(NULL); + + if (shf_close(shf) == EOF) { + i = errno; + close(fd); + fd = errno; + warningf(true, "error writing %s: %s, %s", h->name, + strerror(i), strerror(fd)); + return (-2); /* special to iosetup(): don't print error */ + } + + return (fd); +} + +/* + * ksh special - the select command processing section + * print the args in column form - assuming that we can + */ +static const char * +do_selectargs(const char **ap, bool print_menu) +{ + static const char *read_args[] = { + "read", "-r", "REPLY", NULL + }; + char *s; + int i, argct; + + for (argct = 0; ap[argct]; argct++) + ; + while (1) { + /* Menu is printed if + * - this is the first time around the select loop + * - the user enters a blank line + * - the REPLY parameter is empty + */ + if (print_menu || !*str_val(global("REPLY"))) + pr_menu(ap); + shellf("%s", str_val(global("PS3"))); + if (call_builtin(findcom("read", FC_BI), read_args)) + return (NULL); + s = str_val(global("REPLY")); + if (*s) { + getn(s, &i); + return ((i >= 1 && i <= argct) ? ap[i - 1] : null); + } + print_menu = 1; + } +} + +struct select_menu_info { + const char * const *args; + int num_width; +}; + +static char *select_fmt_entry(char *, int, int, const void *); + +/* format a single select menu item */ +static char * +select_fmt_entry(char *buf, int buflen, int i, const void *arg) +{ + const struct select_menu_info *smi = + (const struct select_menu_info *)arg; + + shf_snprintf(buf, buflen, "%*d) %s", + smi->num_width, i + 1, smi->args[i]); + return (buf); +} + +/* + * print a select style menu + */ +int +pr_menu(const char * const *ap) +{ + struct select_menu_info smi; + const char * const *pp; + int acols = 0, aocts = 0, i, n; + + /* + * width/column calculations were done once and saved, but this + * means select can't be used recursively so we re-calculate + * each time (could save in a structure that is returned, but + * it's probably not worth the bother) + */ + + /* + * get dimensions of the list + */ + for (n = 0, pp = ap; *pp; n++, pp++) { + i = strlen(*pp); + if (i > aocts) + aocts = i; + i = utf_mbswidth(*pp); + if (i > acols) + acols = i; + } + + /* + * we will print an index of the form "%d) " in front of + * each entry, so get the maximum width of this + */ + for (i = n, smi.num_width = 1; i >= 10; i /= 10) + smi.num_width++; + + smi.args = ap; + print_columns(shl_out, n, select_fmt_entry, (void *)&smi, + smi.num_width + 2 + aocts, smi.num_width + 2 + acols, + true); + + return (n); +} + +/* XXX: horrible kludge to fit within the framework */ +static char *plain_fmt_entry(char *, int, int, const void *); + +static char * +plain_fmt_entry(char *buf, int buflen, int i, const void *arg) +{ + shf_snprintf(buf, buflen, "%s", ((const char * const *)arg)[i]); + return (buf); +} + +int +pr_list(char * const *ap) +{ + int acols = 0, aocts = 0, i, n; + char * const *pp; + + for (n = 0, pp = ap; *pp; n++, pp++) { + i = strlen(*pp); + if (i > aocts) + aocts = i; + i = utf_mbswidth(*pp); + if (i > acols) + acols = i; + } + + print_columns(shl_out, n, plain_fmt_entry, (const void *)ap, + aocts, acols, false); + + return (n); +} + +/* + * [[ ... ]] evaluation routines + */ + +/* + * Test if the current token is a whatever. Accepts the current token if + * it is. Returns 0 if it is not, non-zero if it is (in the case of + * TM_UNOP and TM_BINOP, the returned value is a Test_op). + */ +static Test_op +dbteste_isa(Test_env *te, Test_meta meta) +{ + Test_op ret = TO_NONOP; + int uqword; + const char *p; + + if (!*te->pos.wp) + return (meta == TM_END ? TO_NONNULL : TO_NONOP); + + /* unquoted word? */ + for (p = *te->pos.wp; *p == CHAR; p += 2) + ; + uqword = *p == EOS; + + if (meta == TM_UNOP || meta == TM_BINOP) { + if (uqword) { + char buf[8]; /* longer than the longest operator */ + char *q = buf; + for (p = *te->pos.wp; + *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2) + *q++ = p[1]; + *q = '\0'; + ret = test_isop(meta, buf); + } + } else if (meta == TM_END) + ret = TO_NONOP; + else + ret = (uqword && !strcmp(*te->pos.wp, + dbtest_tokens[(int)meta])) ? TO_NONNULL : TO_NONOP; + + /* Accept the token? */ + if (ret != TO_NONOP) + te->pos.wp++; + + return (ret); +} + +static const char * +dbteste_getopnd(Test_env *te, Test_op op, bool do_eval) +{ + const char *s = *te->pos.wp; + + if (!s) + return (NULL); + + te->pos.wp++; + + if (!do_eval) + return (null); + + if (op == TO_STEQL || op == TO_STNEQ) + s = evalstr(s, DOTILDE | DOPAT); + else + s = evalstr(s, DOTILDE); + + return (s); +} + +static void +dbteste_error(Test_env *te, int offset, const char *msg) +{ + te->flags |= TEF_ERROR; + internal_warningf("dbteste_error: %s (offset %d)", msg, offset); +} diff --git a/mksh/src/expr.c b/mksh/src/expr.c new file mode 100644 index 0000000..6c5710c --- /dev/null +++ b/mksh/src/expr.c @@ -0,0 +1,895 @@ +/* $OpenBSD: expr.c,v 1.21 2009/06/01 19:00:57 deraadt Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.44 2010/08/14 21:35:13 tg Exp $"); + +/* The order of these enums is constrained by the order of opinfo[] */ +enum token { + /* some (long) unary operators */ + O_PLUSPLUS = 0, O_MINUSMINUS, + /* binary operators */ + O_EQ, O_NE, + /* assignments are assumed to be in range O_ASN .. O_BORASN */ + O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN, + O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN, + O_LSHIFT, O_RSHIFT, + O_LE, O_GE, O_LT, O_GT, + O_LAND, + O_LOR, + O_TIMES, O_DIV, O_MOD, + O_PLUS, O_MINUS, + O_BAND, + O_BXOR, + O_BOR, + O_TERN, + O_COMMA, + /* things after this aren't used as binary operators */ + /* unary that are not also binaries */ + O_BNOT, O_LNOT, + /* misc */ + OPEN_PAREN, CLOSE_PAREN, CTERN, + /* things that don't appear in the opinfo[] table */ + VAR, LIT, END, BAD +}; +#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA) +#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN) + +/* precisions; used to be enum prec but we do arithmetics on it */ +#define P_PRIMARY 0 /* VAR, LIT, (), ~ ! - + */ +#define P_MULT 1 /* * / % */ +#define P_ADD 2 /* + - */ +#define P_SHIFT 3 /* << >> */ +#define P_RELATION 4 /* < <= > >= */ +#define P_EQUALITY 5 /* == != */ +#define P_BAND 6 /* & */ +#define P_BXOR 7 /* ^ */ +#define P_BOR 8 /* | */ +#define P_LAND 9 /* && */ +#define P_LOR 10 /* || */ +#define P_TERN 11 /* ?: */ +#define P_ASSIGN 12 /* = *= /= %= += -= <<= >>= &= ^= |= */ +#define P_COMMA 13 /* , */ +#define MAX_PREC P_COMMA + +struct opinfo { + char name[4]; + int len; /* name length */ + int prec; /* precedence: lower is higher */ +}; + +/* Tokens in this table must be ordered so the longest are first + * (eg, += before +). If you change something, change the order + * of enum token too. + */ +static const struct opinfo opinfo[] = { + { "++", 2, P_PRIMARY }, /* before + */ + { "--", 2, P_PRIMARY }, /* before - */ + { "==", 2, P_EQUALITY }, /* before = */ + { "!=", 2, P_EQUALITY }, /* before ! */ + { "=", 1, P_ASSIGN }, /* keep assigns in a block */ + { "*=", 2, P_ASSIGN }, + { "/=", 2, P_ASSIGN }, + { "%=", 2, P_ASSIGN }, + { "+=", 2, P_ASSIGN }, + { "-=", 2, P_ASSIGN }, + { "<<=", 3, P_ASSIGN }, + { ">>=", 3, P_ASSIGN }, + { "&=", 2, P_ASSIGN }, + { "^=", 2, P_ASSIGN }, + { "|=", 2, P_ASSIGN }, + { "<<", 2, P_SHIFT }, + { ">>", 2, P_SHIFT }, + { "<=", 2, P_RELATION }, + { ">=", 2, P_RELATION }, + { "<", 1, P_RELATION }, + { ">", 1, P_RELATION }, + { "&&", 2, P_LAND }, + { "||", 2, P_LOR }, + { "*", 1, P_MULT }, + { "/", 1, P_MULT }, + { "%", 1, P_MULT }, + { "+", 1, P_ADD }, + { "-", 1, P_ADD }, + { "&", 1, P_BAND }, + { "^", 1, P_BXOR }, + { "|", 1, P_BOR }, + { "?", 1, P_TERN }, + { ",", 1, P_COMMA }, + { "~", 1, P_PRIMARY }, + { "!", 1, P_PRIMARY }, + { "(", 1, P_PRIMARY }, + { ")", 1, P_PRIMARY }, + { ":", 1, P_PRIMARY }, + { "", 0, P_PRIMARY } +}; + +typedef struct expr_state Expr_state; +struct expr_state { + const char *expression; /* expression being evaluated */ + const char *tokp; /* lexical position */ + struct tbl *val; /* value from token() */ + struct tbl *evaling; /* variable that is being recursively + * expanded (EXPRINEVAL flag set) */ + int noassign; /* don't do assigns (for ?:,&&,||) */ + enum token tok; /* token from token() */ + bool arith; /* evaluating an $(()) expression? */ + bool natural; /* unsigned arithmetic calculation */ +}; + +#define bivui(x, op, y) (es->natural ? \ + (mksh_ari_t)((x)->val.u op (y)->val.u) : \ + (mksh_ari_t)((x)->val.i op (y)->val.i) \ +) +#define chvui(x, op) do { \ + if (es->natural) \ + (x)->val.u = op (x)->val.u; \ + else \ + (x)->val.i = op (x)->val.i; \ +} while (/* CONSTCOND */ 0) +#define stvui(x, n) do { \ + if (es->natural) \ + (x)->val.u = (n); \ + else \ + (x)->val.i = (n); \ +} while (/* CONSTCOND */ 0) + +enum error_type { + ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE, + ET_LVALUE, ET_RDONLY, ET_STR +}; + +static void evalerr(Expr_state *, enum error_type, const char *) + MKSH_A_NORETURN; +static struct tbl *evalexpr(Expr_state *, int); +static void exprtoken(Expr_state *); +static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool); +static void assign_check(Expr_state *, enum token, struct tbl *); +static struct tbl *tempvar(void); +static struct tbl *intvar(Expr_state *, struct tbl *); + +/* + * parse and evaluate expression + */ +int +evaluate(const char *expr, mksh_ari_t *rval, int error_ok, bool arith) +{ + struct tbl v; + int ret; + + v.flag = DEFINED|INTEGER; + v.type = 0; + ret = v_evaluate(&v, expr, error_ok, arith); + *rval = v.val.i; + return (ret); +} + +/* + * parse and evaluate expression, storing result in vp. + */ +int +v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok, + bool arith) +{ + struct tbl *v; + Expr_state curstate; + Expr_state * const es = &curstate; + int i; + + /* save state to allow recursive calls */ + curstate.expression = curstate.tokp = expr; + curstate.noassign = 0; + curstate.arith = arith; + curstate.evaling = NULL; + curstate.natural = false; + + newenv(E_ERRH); + i = sigsetjmp(e->jbuf, 0); + if (i) { + /* Clear EXPRINEVAL in of any variables we were playing with */ + if (curstate.evaling) + curstate.evaling->flag &= ~EXPRINEVAL; + quitenv(NULL); + if (i == LAEXPR) { + if (error_ok == KSH_RETURN_ERROR) + return (0); + errorfz(); + } + unwind(i); + /* NOTREACHED */ + } + + exprtoken(es); + if (es->tok == END) { + es->tok = LIT; + es->val = tempvar(); + } + v = intvar(es, evalexpr(es, MAX_PREC)); + + if (es->tok != END) + evalerr(es, ET_UNEXPECTED, NULL); + + if (es->arith && es->natural) + vp->flag |= INT_U; + if (vp->flag & INTEGER) + setint_v(vp, v, es->arith); + else + /* can fail if readonly */ + setstr(vp, str_val(v), error_ok); + + quitenv(NULL); + + return (1); +} + +static void +evalerr(Expr_state *es, enum error_type type, const char *str) +{ + char tbuf[2]; + const char *s; + + es->arith = false; + switch (type) { + case ET_UNEXPECTED: + switch (es->tok) { + case VAR: + s = es->val->name; + break; + case LIT: + s = str_val(es->val); + break; + case END: + s = "end of expression"; + break; + case BAD: + tbuf[0] = *es->tokp; + tbuf[1] = '\0'; + s = tbuf; + break; + default: + s = opinfo[(int)es->tok].name; + } + warningf(true, "%s: unexpected '%s'", es->expression, s); + break; + + case ET_BADLIT: + warningf(true, "%s: bad number '%s'", es->expression, str); + break; + + case ET_RECURSIVE: + warningf(true, "%s: expression recurses on parameter '%s'", + es->expression, str); + break; + + case ET_LVALUE: + warningf(true, "%s: %s requires lvalue", + es->expression, str); + break; + + case ET_RDONLY: + warningf(true, "%s: %s applied to read only variable", + es->expression, str); + break; + + default: /* keep gcc happy */ + case ET_STR: + warningf(true, "%s: %s", es->expression, str); + break; + } + unwind(LAEXPR); +} + +static struct tbl * +evalexpr(Expr_state *es, int prec) +{ + struct tbl *vl, *vr = NULL, *vasn; + enum token op; + mksh_ari_t res = 0; + + if (prec == P_PRIMARY) { + op = es->tok; + if (op == O_BNOT || op == O_LNOT || op == O_MINUS || + op == O_PLUS) { + exprtoken(es); + vl = intvar(es, evalexpr(es, P_PRIMARY)); + if (op == O_BNOT) + chvui(vl, ~); + else if (op == O_LNOT) + chvui(vl, !); + else if (op == O_MINUS) + chvui(vl, -); + /* op == O_PLUS is a no-op */ + } else if (op == OPEN_PAREN) { + exprtoken(es); + vl = evalexpr(es, MAX_PREC); + if (es->tok != CLOSE_PAREN) + evalerr(es, ET_STR, "missing )"); + exprtoken(es); + } else if (op == O_PLUSPLUS || op == O_MINUSMINUS) { + exprtoken(es); + vl = do_ppmm(es, op, es->val, true); + exprtoken(es); + } else if (op == VAR || op == LIT) { + vl = es->val; + exprtoken(es); + } else { + evalerr(es, ET_UNEXPECTED, NULL); + /* NOTREACHED */ + } + if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) { + vl = do_ppmm(es, es->tok, vl, false); + exprtoken(es); + } + return (vl); + } + vl = evalexpr(es, prec - 1); + for (op = es->tok; IS_BINOP(op) && opinfo[(int)op].prec == prec; + op = es->tok) { + exprtoken(es); + vasn = vl; + if (op != O_ASN) /* vl may not have a value yet */ + vl = intvar(es, vl); + if (IS_ASSIGNOP(op)) { + assign_check(es, op, vasn); + vr = intvar(es, evalexpr(es, P_ASSIGN)); + } else if (op != O_TERN && op != O_LAND && op != O_LOR) + vr = intvar(es, evalexpr(es, prec - 1)); + if ((op == O_DIV || op == O_MOD || op == O_DIVASN || + op == O_MODASN) && vr->val.i == 0) { + if (es->noassign) + vr->val.i = 1; + else + evalerr(es, ET_STR, "zero divisor"); + } + switch ((int)op) { + case O_TIMES: + case O_TIMESASN: + res = bivui(vl, *, vr); + break; + case O_DIV: + case O_DIVASN: + res = bivui(vl, /, vr); + break; + case O_MOD: + case O_MODASN: + res = bivui(vl, %, vr); + break; + case O_PLUS: + case O_PLUSASN: + res = bivui(vl, +, vr); + break; + case O_MINUS: + case O_MINUSASN: + res = bivui(vl, -, vr); + break; + case O_LSHIFT: + case O_LSHIFTASN: + res = bivui(vl, <<, vr); + break; + case O_RSHIFT: + case O_RSHIFTASN: + res = bivui(vl, >>, vr); + break; + case O_LT: + res = bivui(vl, <, vr); + break; + case O_LE: + res = bivui(vl, <=, vr); + break; + case O_GT: + res = bivui(vl, >, vr); + break; + case O_GE: + res = bivui(vl, >=, vr); + break; + case O_EQ: + res = bivui(vl, ==, vr); + break; + case O_NE: + res = bivui(vl, !=, vr); + break; + case O_BAND: + case O_BANDASN: + res = bivui(vl, &, vr); + break; + case O_BXOR: + case O_BXORASN: + res = bivui(vl, ^, vr); + break; + case O_BOR: + case O_BORASN: + res = bivui(vl, |, vr); + break; + case O_LAND: + if (!vl->val.i) + es->noassign++; + vr = intvar(es, evalexpr(es, prec - 1)); + res = bivui(vl, &&, vr); + if (!vl->val.i) + es->noassign--; + break; + case O_LOR: + if (vl->val.i) + es->noassign++; + vr = intvar(es, evalexpr(es, prec - 1)); + res = bivui(vl, ||, vr); + if (vl->val.i) + es->noassign--; + break; + case O_TERN: + { + bool ev = vl->val.i != 0; + + if (!ev) + es->noassign++; + vl = evalexpr(es, MAX_PREC); + if (!ev) + es->noassign--; + if (es->tok != CTERN) + evalerr(es, ET_STR, "missing :"); + exprtoken(es); + if (ev) + es->noassign++; + vr = evalexpr(es, P_TERN); + if (ev) + es->noassign--; + vl = ev ? vl : vr; + } + break; + case O_ASN: + res = vr->val.i; + break; + case O_COMMA: + res = vr->val.i; + break; + } + if (IS_ASSIGNOP(op)) { + stvui(vr, res); + if (!es->noassign) { + if (vasn->flag & INTEGER) + setint_v(vasn, vr, es->arith); + else + setint(vasn, res); + } + vl = vr; + } else if (op != O_TERN) + stvui(vl, res); + } + return (vl); +} + +static void +exprtoken(Expr_state *es) +{ + const char *cp = es->tokp; + int c; + char *tvar; + + /* skip white space */ + skip_spaces: + while ((c = *cp), ksh_isspace(c)) + ++cp; + if (es->tokp == es->expression && c == '#') { + /* expression begins with # */ + es->natural = true; /* switch to unsigned */ + ++cp; + goto skip_spaces; + } + es->tokp = cp; + + if (c == '\0') + es->tok = END; + else if (ksh_isalphx(c)) { + for (; ksh_isalnux(c); c = *cp) + cp++; + if (c == '[') { + int len; + + len = array_ref_len(cp); + if (len == 0) + evalerr(es, ET_STR, "missing ]"); + cp += len; + } else if (c == '(' /*)*/ ) { + /* todo: add math functions (all take single argument): + * abs acos asin atan cos cosh exp int log sin sinh sqrt + * tan tanh + */ + ; + } + if (es->noassign) { + es->val = tempvar(); + es->val->flag |= EXPRLVALUE; + } else { + strndupx(tvar, es->tokp, cp - es->tokp, ATEMP); + es->val = global(tvar); + afree(tvar, ATEMP); + } + es->tok = VAR; + } else if (c == '1' && cp[1] == '#') { + cp += 2; + cp += utf_ptradj(cp); + strndupx(tvar, es->tokp, cp - es->tokp, ATEMP); + goto process_tvar; +#ifndef MKSH_SMALL + } else if (c == '\'') { + ++cp; + cp += utf_ptradj(cp); + if (*cp++ != '\'') + evalerr(es, ET_STR, + "multi-character character constant"); + /* 'x' -> 1#x (x = one multibyte character) */ + c = cp - es->tokp; + tvar = alloc(c + /* NUL */ 1, ATEMP); + tvar[0] = '1'; + tvar[1] = '#'; + memcpy(tvar + 2, es->tokp + 1, c - 2); + tvar[c] = '\0'; + goto process_tvar; +#endif + } else if (ksh_isdigit(c)) { + while (c != '_' && (ksh_isalnux(c) || c == '#')) + c = *cp++; + strndupx(tvar, es->tokp, --cp - es->tokp, ATEMP); + process_tvar: + es->val = tempvar(); + es->val->flag &= ~INTEGER; + es->val->type = 0; + es->val->val.s = tvar; + if (setint_v(es->val, es->val, es->arith) == NULL) + evalerr(es, ET_BADLIT, tvar); + afree(tvar, ATEMP); + es->tok = LIT; + } else { + int i, n0; + + for (i = 0; (n0 = opinfo[i].name[0]); i++) + if (c == n0 && strncmp(cp, opinfo[i].name, + (size_t)opinfo[i].len) == 0) { + es->tok = (enum token)i; + cp += opinfo[i].len; + break; + } + if (!n0) + es->tok = BAD; + } + es->tokp = cp; +} + +/* Do a ++ or -- operation */ +static struct tbl * +do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix) +{ + struct tbl *vl; + mksh_ari_t oval; + + assign_check(es, op, vasn); + + vl = intvar(es, vasn); + oval = vl->val.i; + if (op == O_PLUSPLUS) { + if (es->natural) + ++vl->val.u; + else + ++vl->val.i; + } else { + if (es->natural) + --vl->val.u; + else + --vl->val.i; + } + if (vasn->flag & INTEGER) + setint_v(vasn, vl, es->arith); + else + setint(vasn, vl->val.i); + if (!is_prefix) /* undo the inc/dec */ + vl->val.i = oval; + + return (vl); +} + +static void +assign_check(Expr_state *es, enum token op, struct tbl *vasn) +{ + if (es->tok == END || + (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))) + evalerr(es, ET_LVALUE, opinfo[(int)op].name); + else if (vasn->flag & RDONLY) + evalerr(es, ET_RDONLY, opinfo[(int)op].name); +} + +static struct tbl * +tempvar(void) +{ + struct tbl *vp; + + vp = alloc(sizeof(struct tbl), ATEMP); + vp->flag = ISSET|INTEGER; + vp->type = 0; + vp->areap = ATEMP; + vp->ua.hval = 0; + vp->val.i = 0; + vp->name[0] = '\0'; + return (vp); +} + +/* cast (string) variable to temporary integer variable */ +static struct tbl * +intvar(Expr_state *es, struct tbl *vp) +{ + struct tbl *vq; + + /* try to avoid replacing a temp var with another temp var */ + if (vp->name[0] == '\0' && + (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER)) + return (vp); + + vq = tempvar(); + if (setint_v(vq, vp, es->arith) == NULL) { + if (vp->flag & EXPRINEVAL) + evalerr(es, ET_RECURSIVE, vp->name); + es->evaling = vp; + vp->flag |= EXPRINEVAL; + v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith); + vp->flag &= ~EXPRINEVAL; + es->evaling = NULL; + } + return (vq); +} + + +/* + * UTF-8 support code: high-level functions + */ + +int +utf_widthadj(const char *src, const char **dst) +{ + size_t len; + unsigned int wc; + int width; + + if (!UTFMODE || (len = utf_mbtowc(&wc, src)) == (size_t)-1 || + wc == 0) + len = width = 1; + else if ((width = utf_wcwidth(wc)) < 0) + /* XXX use 2 for x_zotc3 here? */ + width = 1; + + if (dst) + *dst = src + len; + return (width); +} + +int +utf_mbswidth(const char *s) +{ + size_t len; + unsigned int wc; + int width = 0, cw; + + if (!UTFMODE) + return (strlen(s)); + + while (*s) + if (((len = utf_mbtowc(&wc, s)) == (size_t)-1) || + ((cw = utf_wcwidth(wc)) == -1)) { + s++; + width += 1; + } else { + s += len; + width += cw; + } + return (width); +} + +const char * +utf_skipcols(const char *p, int cols) +{ + int c = 0; + + while (c < cols) { + if (!*p) + return (p + cols - c); + c += utf_widthadj(p, &p); + } + return (p); +} + +size_t +utf_ptradj(const char *src) +{ + register size_t n; + + if (!UTFMODE || + *(const unsigned char *)(src) < 0xC2 || + (n = utf_mbtowc(NULL, src)) == (size_t)-1) + n = 1; + return (n); +} + +/* + * UTF-8 support code: low-level functions + */ + +/* CESU-8 multibyte and wide character conversion crafted for mksh */ + +size_t +utf_mbtowc(unsigned int *dst, const char *src) +{ + const unsigned char *s = (const unsigned char *)src; + unsigned int c, wc; + + if ((wc = *s++) < 0x80) { + out: + if (dst != NULL) + *dst = wc; + return (wc ? ((const char *)s - src) : 0); + } + if (wc < 0xC2 || wc >= 0xF0) + /* < 0xC0: spurious second byte */ + /* < 0xC2: non-minimalistic mapping error in 2-byte seqs */ + /* > 0xEF: beyond BMP */ + goto ilseq; + + if (wc < 0xE0) { + wc = (wc & 0x1F) << 6; + if (((c = *s++) & 0xC0) != 0x80) + goto ilseq; + wc |= c & 0x3F; + goto out; + } + + wc = (wc & 0x0F) << 12; + + if (((c = *s++) & 0xC0) != 0x80) + goto ilseq; + wc |= (c & 0x3F) << 6; + + if (((c = *s++) & 0xC0) != 0x80) + goto ilseq; + wc |= c & 0x3F; + + /* Check for non-minimalistic mapping error in 3-byte seqs */ + if (wc >= 0x0800 && wc <= 0xFFFD) + goto out; + ilseq: + return ((size_t)(-1)); +} + +size_t +utf_wctomb(char *dst, unsigned int wc) +{ + unsigned char *d; + + if (wc < 0x80) { + *dst = wc; + return (1); + } + + d = (unsigned char *)dst; + if (wc < 0x0800) + *d++ = (wc >> 6) | 0xC0; + else { + *d++ = ((wc = wc > 0xFFFD ? 0xFFFD : wc) >> 12) | 0xE0; + *d++ = ((wc >> 6) & 0x3F) | 0x80; + } + *d++ = (wc & 0x3F) | 0x80; + return ((char *)d - dst); +} + + +#ifndef MKSH_mirbsd_wcwidth +/* --- begin of wcwidth.c excerpt --- */ +/*- + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + */ + +__RCSID("$miros: src/lib/libc/i18n/wcwidth.c,v 1.8 2008/09/20 12:01:18 tg Exp $"); + +int +utf_wcwidth(unsigned int c) +{ + static const struct cbset { + unsigned short first; + unsigned short last; + } comb[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB } + }; + size_t min = 0, mid, max = NELEM(comb) - 1; + + /* test for 8-bit control characters */ + if (c < 32 || (c >= 0x7f && c < 0xa0)) + return (c ? -1 : 0); + + /* binary search in table of non-spacing characters */ + if (c >= comb[0].first && c <= comb[max].last) + while (max >= min) { + mid = (min + max) / 2; + if (c > comb[mid].last) + min = mid + 1; + else if (c < comb[mid].first) + max = mid - 1; + else + return (0); + } + + /* if we arrive here, c is not a combining or C0/C1 control char */ + return ((c >= 0x1100 && ( + c <= 0x115f || /* Hangul Jamo init. consonants */ + c == 0x2329 || c == 0x232a || + (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) || /* CJK ... Yi */ + (c >= 0xac00 && c <= 0xd7a3) || /* Hangul Syllables */ + (c >= 0xf900 && c <= 0xfaff) || /* CJK Compatibility Ideographs */ + (c >= 0xfe10 && c <= 0xfe19) || /* Vertical forms */ + (c >= 0xfe30 && c <= 0xfe6f) || /* CJK Compatibility Forms */ + (c >= 0xff00 && c <= 0xff60) || /* Fullwidth Forms */ + (c >= 0xffe0 && c <= 0xffe6))) ? 2 : 1); +} +/* --- end of wcwidth.c excerpt --- */ +#endif diff --git a/mksh/src/funcs.c b/mksh/src/funcs.c new file mode 100644 index 0000000..9d9c03a --- /dev/null +++ b/mksh/src/funcs.c @@ -0,0 +1,3429 @@ +/* $OpenBSD: c_ksh.c,v 1.33 2009/02/07 14:03:24 kili Exp $ */ +/* $OpenBSD: c_sh.c,v 1.41 2010/03/27 09:10:01 jmc Exp $ */ +/* $OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto Exp $ */ +/* $OpenBSD: c_ulimit.c,v 1.17 2008/03/21 12:51:19 millert Exp $ */ + +/*- + * Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.157 2010/08/24 14:42:01 tg Exp $"); + +#if HAVE_KILLPG +/* + * use killpg if < -1 since -1 does special things + * for some non-killpg-endowed kills + */ +#define mksh_kill(p,s) ((p) < -1 ? killpg(-(p), (s)) : kill((p), (s))) +#else +/* cross fingers and hope kill is killpg-endowed */ +#define mksh_kill kill +#endif + +/* XXX conditions correct? */ +#if !defined(RLIM_INFINITY) && !defined(MKSH_NO_LIMITS) +#define MKSH_NO_LIMITS +#endif + +#ifdef MKSH_NO_LIMITS +#define c_ulimit c_label +#endif + +extern uint8_t set_refflag; + +/* A leading = means assignments before command are kept; + * a leading * means a POSIX special builtin; + * a leading + means a POSIX regular builtin + * (* and + should not be combined). + */ +const struct builtin mkshbuiltins[] = { + {"*=.", c_dot}, + {"*=:", c_label}, + {"[", c_test}, + {"*=break", c_brkcont}, + {"=builtin", c_builtin}, + {"*=continue", c_brkcont}, + {"*=eval", c_eval}, + {"*=exec", c_exec}, + {"*=exit", c_exitreturn}, + {"+false", c_label}, + {"*=return", c_exitreturn}, + {"*=set", c_set}, + {"*=shift", c_shift}, + {"=times", c_times}, + {"*=trap", c_trap}, + {"+=wait", c_wait}, + {"+read", c_read}, + {"test", c_test}, + {"+true", c_label}, + {"ulimit", c_ulimit}, + {"+umask", c_umask}, + {"*=unset", c_unset}, + {"+alias", c_alias}, /* no =: AT&T manual wrong */ + {"+cd", c_cd}, + {"chdir", c_cd}, /* dash compatibility hack */ + {"+command", c_command}, + {"echo", c_print}, + {"*=export", c_typeset}, + {"+fc", c_fc}, + {"+getopts", c_getopts}, + {"+jobs", c_jobs}, + {"+kill", c_kill}, + {"let", c_let}, + {"print", c_print}, +#ifdef MKSH_PRINTF_BUILTIN + {"printf", c_printf}, +#endif + {"pwd", c_pwd}, + {"*=readonly", c_typeset}, + {T__typeset, c_typeset}, + {"+unalias", c_unalias}, + {"whence", c_whence}, +#ifndef MKSH_UNEMPLOYED + {"+bg", c_fgbg}, + {"+fg", c_fgbg}, +#endif + {"bind", c_bind}, +#if HAVE_MKNOD + {"mknod", c_mknod}, +#endif + {"realpath", c_realpath}, + {"rename", c_rename}, + {NULL, (int (*)(const char **))NULL} +}; + +struct kill_info { + int num_width; + int name_width; +}; + +static const struct t_op { + char op_text[4]; + Test_op op_num; +} u_ops[] = { + {"-a", TO_FILAXST }, + {"-b", TO_FILBDEV }, + {"-c", TO_FILCDEV }, + {"-d", TO_FILID }, + {"-e", TO_FILEXST }, + {"-f", TO_FILREG }, + {"-G", TO_FILGID }, + {"-g", TO_FILSETG }, + {"-h", TO_FILSYM }, + {"-H", TO_FILCDF }, + {"-k", TO_FILSTCK }, + {"-L", TO_FILSYM }, + {"-n", TO_STNZE }, + {"-O", TO_FILUID }, + {"-o", TO_OPTION }, + {"-p", TO_FILFIFO }, + {"-r", TO_FILRD }, + {"-s", TO_FILGZ }, + {"-S", TO_FILSOCK }, + {"-t", TO_FILTT }, + {"-u", TO_FILSETU }, + {"-w", TO_FILWR }, + {"-x", TO_FILEX }, + {"-z", TO_STZER }, + {"", TO_NONOP } +}; +static const struct t_op b_ops[] = { + {"=", TO_STEQL }, + {"==", TO_STEQL }, + {"!=", TO_STNEQ }, + {"<", TO_STLT }, + {">", TO_STGT }, + {"-eq", TO_INTEQ }, + {"-ne", TO_INTNE }, + {"-gt", TO_INTGT }, + {"-ge", TO_INTGE }, + {"-lt", TO_INTLT }, + {"-le", TO_INTLE }, + {"-ef", TO_FILEQ }, + {"-nt", TO_FILNT }, + {"-ot", TO_FILOT }, + {"", TO_NONOP } +}; + +static int test_eaccess(const char *, int); +static int test_oexpr(Test_env *, bool); +static int test_aexpr(Test_env *, bool); +static int test_nexpr(Test_env *, bool); +static int test_primary(Test_env *, bool); +static Test_op ptest_isa(Test_env *, Test_meta); +static const char *ptest_getopnd(Test_env *, Test_op, bool); +static void ptest_error(Test_env *, int, const char *); +static char *kill_fmt_entry(char *, int, int, const void *); +static void p_time(struct shf *, bool, long, int, int, + const char *, const char *) + MKSH_A_NONNULL((nonnull (6, 7))); +static char *do_realpath(const char *); + +static char * +do_realpath(const char *upath) +{ + char *xp, *ip, *tp, *ipath, *ldest = NULL; + XString xs; + ptrdiff_t pos; + size_t len; + int symlinks = 32; /* max. recursion depth */ + int llen; + struct stat sb; +#ifdef NO_PATH_MAX + size_t ldestlen = 0; +#define pathlen sb.st_size +#define pathcnd (ldestlen < (pathlen + 1)) +#else +#define pathlen PATH_MAX +#define pathcnd (!ldest) +#endif + + if (upath[0] == '/') { + /* upath is an absolute pathname */ + strdupx(ipath, upath, ATEMP); + } else { + /* upath is a relative pathname, prepend cwd */ + if ((tp = ksh_get_wd(NULL)) == NULL || tp[0] != '/') + return (NULL); + ipath = shf_smprintf("%s/%s", tp, upath); + afree(tp, ATEMP); + } + + Xinit(xs, xp, strlen(ip = ipath) + 1, ATEMP); + + while (*ip) { + /* skip slashes in input */ + while (*ip == '/') + ++ip; + if (!*ip) + break; + + /* get next pathname component from input */ + tp = ip; + while (*ip && *ip != '/') + ++ip; + len = ip - tp; + + /* check input for "." and ".." */ + if (tp[0] == '.') { + if (len == 1) + /* just continue with the next one */ + continue; + else if (len == 2 && tp[1] == '.') { + /* strip off last pathname component */ + while (xp > Xstring(xs, xp)) + if (*--xp == '/') + break; + /* then continue with the next one */ + continue; + } + } + + /* store output position away, then append slash to output */ + pos = Xsavepos(xs, xp); + /* 1 for the '/' and len + 1 for tp and the NUL from below */ + XcheckN(xs, xp, 1 + len + 1); + Xput(xs, xp, '/'); + + /* append next pathname component to output */ + memcpy(xp, tp, len); + xp += len; + *xp = '\0'; + + /* lstat the current output, see if it's a symlink */ + if (lstat(Xstring(xs, xp), &sb)) { + /* lstat failed */ + if (errno == ENOENT) { + /* because the pathname does not exist */ + while (*ip == '/') + /* skip any trailing slashes */ + ++ip; + /* no more components left? */ + if (!*ip) + /* we can still return successfully */ + break; + /* more components left? fall through */ + } + /* not ENOENT or not at the end of ipath */ + goto notfound; + } + + /* check if we encountered a symlink? */ + if (S_ISLNK(sb.st_mode)) { + /* reached maximum recursion depth? */ + if (!symlinks--) { + /* yep, prevent infinite loops */ + errno = ELOOP; + goto notfound; + } + + /* get symlink(7) target */ + if (pathcnd) + ldest = aresize(ldest, pathlen + 1, ATEMP); + llen = readlink(Xstring(xs, xp), ldest, pathlen); + if (llen < 0) + /* oops... */ + goto notfound; + ldest[llen] = '\0'; + + /* + * restart if symlink target is an absolute path, + * otherwise continue with currently resolved prefix + */ + xp = (ldest[0] == '/') ? Xstring(xs, xp) : + Xrestpos(xs, xp, pos); + tp = shf_smprintf("%s%s%s", ldest, *ip ? "/" : "", ip); + afree(ipath, ATEMP); + ip = ipath = tp; + } + /* otherwise (no symlink) merely go on */ + } + + /* + * either found the target and successfully resolved it, + * or found its parent directory and may create it + */ + if (Xlength(xs, xp) == 0) + /* + * if the resolved pathname is "", make it "/", + * otherwise do not add a trailing slash + */ + Xput(xs, xp, '/'); + Xput(xs, xp, '\0'); + + /* + * if source path had a trailing slash, check if target path + * is not a non-directory existing file + */ + if (ip > ipath && ip[-1] == '/') { + if (stat(Xstring(xs, xp), &sb)) { + if (errno != ENOENT) + goto notfound; + } else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto notfound; + } + /* target now either does not exist or is a directory */ + } + + /* return target path */ + if (ldest != NULL) + afree(ldest, ATEMP); + afree(ipath, ATEMP); + return (Xclose(xs, xp)); + + notfound: + llen = errno; /* save; free(3) might trash it */ + if (ldest != NULL) + afree(ldest, ATEMP); + afree(ipath, ATEMP); + Xfree(xs, xp); + errno = llen; + return (NULL); + +#undef pathlen +#undef pathcnd +} + +int +c_cd(const char **wp) +{ + int optc, rv, phys_path; + bool physical = Flag(FPHYSICAL) ? true : false; + int cdnode; /* was a node from cdpath added in? */ + bool printpath = false; /* print where we cd'd? */ + struct tbl *pwd_s, *oldpwd_s; + XString xs; + char *dir, *allocd = NULL, *tryp, *pwd, *cdpath; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) + switch (optc) { + case 'L': + physical = false; + break; + case 'P': + physical = true; + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + + if (Flag(FRESTRICTED)) { + bi_errorf("restricted shell - can't cd"); + return (1); + } + + pwd_s = global("PWD"); + oldpwd_s = global("OLDPWD"); + + if (!wp[0]) { + /* No arguments - go home */ + if ((dir = str_val(global("HOME"))) == null) { + bi_errorf("no home directory (HOME not set)"); + return (1); + } + } else if (!wp[1]) { + /* One argument: - or dir */ + strdupx(allocd, wp[0], ATEMP); + if (ksh_isdash((dir = allocd))) { + afree(allocd, ATEMP); + allocd = NULL; + dir = str_val(oldpwd_s); + if (dir == null) { + bi_errorf("no OLDPWD"); + return (1); + } + printpath = true; + } + } else if (!wp[2]) { + /* Two arguments - substitute arg1 in PWD for arg2 */ + int ilen, olen, nlen, elen; + char *cp; + + if (!current_wd[0]) { + bi_errorf("don't know current directory"); + return (1); + } + /* substitute arg1 for arg2 in current path. + * if the first substitution fails because the cd fails + * we could try to find another substitution. For now + * we don't + */ + if ((cp = strstr(current_wd, wp[0])) == NULL) { + bi_errorf("bad substitution"); + return (1); + } + ilen = cp - current_wd; + olen = strlen(wp[0]); + nlen = strlen(wp[1]); + elen = strlen(current_wd + ilen + olen) + 1; + dir = allocd = alloc(ilen + nlen + elen, ATEMP); + memcpy(dir, current_wd, ilen); + memcpy(dir + ilen, wp[1], nlen); + memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen); + printpath = true; + } else { + bi_errorf("too many arguments"); + return (1); + } + +#ifdef NO_PATH_MAX + /* only a first guess; make_path will enlarge xs if necessary */ + XinitN(xs, 1024, ATEMP); +#else + XinitN(xs, PATH_MAX, ATEMP); +#endif + + cdpath = str_val(global("CDPATH")); + do { + cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); + if (physical) + rv = chdir(tryp = Xstring(xs, xp) + phys_path); + else { + simplify_path(Xstring(xs, xp)); + rv = chdir(tryp = Xstring(xs, xp)); + } + } while (rv < 0 && cdpath != NULL); + + if (rv < 0) { + if (cdnode) + bi_errorf("%s: bad directory", dir); + else + bi_errorf("%s - %s", tryp, strerror(errno)); + afree(allocd, ATEMP); + return (1); + } + + /* allocd (above) => dir, which is no longer used */ + afree(allocd, ATEMP); + allocd = NULL; + + /* Clear out tracked aliases with relative paths */ + flushcom(0); + + /* Set OLDPWD (note: unsetting OLDPWD does not disable this + * setting in AT&T ksh) + */ + if (current_wd[0]) + /* Ignore failure (happens if readonly or integer) */ + setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); + + if (Xstring(xs, xp)[0] != '/') { + pwd = NULL; + } else if (!physical || !(pwd = allocd = do_realpath(Xstring(xs, xp)))) + pwd = Xstring(xs, xp); + + /* Set PWD */ + if (pwd) { + char *ptmp = pwd; + + set_current_wd(ptmp); + /* Ignore failure (happens if readonly or integer) */ + setstr(pwd_s, ptmp, KSH_RETURN_ERROR); + } else { + set_current_wd(null); + pwd = Xstring(xs, xp); + /* XXX unset $PWD? */ + } + if (printpath || cdnode) + shprintf("%s\n", pwd); + + afree(allocd, ATEMP); + return (0); +} + +int +c_pwd(const char **wp) +{ + int optc; + bool physical = Flag(FPHYSICAL) ? true : false; + char *p, *allocd = NULL; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) + switch (optc) { + case 'L': + physical = false; + break; + case 'P': + physical = true; + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + + if (wp[0]) { + bi_errorf("too many arguments"); + return (1); + } + p = current_wd[0] ? (physical ? allocd = do_realpath(current_wd) : + current_wd) : NULL; + if (p && access(p, R_OK) < 0) + p = NULL; + if (!p && !(p = allocd = ksh_get_wd(NULL))) { + bi_errorf("can't get current directory - %s", strerror(errno)); + return (1); + } + shprintf("%s\n", p); + afree(allocd, ATEMP); + return (0); +} + +static const char *s_ptr; +static int s_get(void); +static void s_put(int); + +int +c_print(const char **wp) +{ +#define PO_NL BIT(0) /* print newline */ +#define PO_EXPAND BIT(1) /* expand backslash sequences */ +#define PO_PMINUSMINUS BIT(2) /* print a -- argument */ +#define PO_HIST BIT(3) /* print to history instead of stdout */ +#define PO_COPROC BIT(4) /* printing to coprocess: block SIGPIPE */ + int fd = 1, c; + int flags = PO_EXPAND|PO_NL; + const char *s, *emsg; + XString xs; + char *xp; + + if (wp[0][0] == 'e') { + /* echo builtin */ + wp++; + if (Flag(FPOSIX) || Flag(FSH)) { + /* Debian Policy 10.4 compliant "echo" builtin */ + if (*wp && !strcmp(*wp, "-n")) { + /* we recognise "-n" only as the first arg */ + flags = 0; + wp++; + } else + /* otherwise, we print everything as-is */ + flags = PO_NL; + } else { + int nflags = flags; + + /** + * a compromise between sysV and BSD echo commands: + * escape sequences are enabled by default, and -n, + * -e and -E are recognised if they appear in argu- + * ments with no illegal options (ie, echo -nq will + * print -nq). + * Different from sysV echo since options are reco- + * gnised, different from BSD echo since escape se- + * quences are enabled by default. + */ + + while ((s = *wp) && *s == '-' && s[1]) { + while (*++s) + if (*s == 'n') + nflags &= ~PO_NL; + else if (*s == 'e') + nflags |= PO_EXPAND; + else if (*s == 'E') + nflags &= ~PO_EXPAND; + else + /* + * bad option: don't use + * nflags, print argument + */ + break; + + if (*s) + break; + wp++; + flags = nflags; + } + } + } else { + int optc; + const char *opts = "Rnprsu,"; + + while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) + switch (optc) { + case 'R': /* fake BSD echo command */ + flags |= PO_PMINUSMINUS; + flags &= ~PO_EXPAND; + opts = "ne"; + break; + case 'e': + flags |= PO_EXPAND; + break; + case 'n': + flags &= ~PO_NL; + break; + case 'p': + if ((fd = coproc_getfd(W_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return (1); + } + break; + case 'r': + flags &= ~PO_EXPAND; + break; + case 's': + flags |= PO_HIST; + break; + case 'u': + if (!*(s = builtin_opt.optarg)) + fd = 0; + else if ((fd = check_fd(s, W_OK, &emsg)) < 0) { + bi_errorf("-u: %s: %s", s, emsg); + return (1); + } + break; + case '?': + return (1); + } + + if (!(builtin_opt.info & GI_MINUSMINUS)) { + /* treat a lone - like -- */ + if (wp[builtin_opt.optind] && + ksh_isdash(wp[builtin_opt.optind])) + builtin_opt.optind++; + } else if (flags & PO_PMINUSMINUS) + builtin_opt.optind--; + wp += builtin_opt.optind; + } + + Xinit(xs, xp, 128, ATEMP); + + while (*wp != NULL) { + s = *wp; + while ((c = *s++) != '\0') { + Xcheck(xs, xp); + if ((flags & PO_EXPAND) && c == '\\') { + s_ptr = s; + c = unbksl(false, s_get, s_put); + s = s_ptr; + if (c == -1) { + /* rejected by generic function */ + switch ((c = *s++)) { + case 'c': + flags &= ~PO_NL; + /* AT&T brain damage */ + continue; + case '\0': + s--; + c = '\\'; + break; + default: + Xput(xs, xp, '\\'); + } + } else if ((unsigned int)c > 0xFF) { + /* generic function returned Unicode */ + char ts[4]; + + c = utf_wctomb(ts, c - 0x100); + ts[c] = 0; + for (c = 0; ts[c]; ++c) + Xput(xs, xp, ts[c]); + continue; + } + } + Xput(xs, xp, c); + } + if (*++wp != NULL) + Xput(xs, xp, ' '); + } + if (flags & PO_NL) + Xput(xs, xp, '\n'); + + if (flags & PO_HIST) { + Xput(xs, xp, '\0'); + histsave(&source->line, Xstring(xs, xp), true, false); + Xfree(xs, xp); + } else { + int len = Xlength(xs, xp); + int opipe = 0; + + /* Ensure we aren't killed by a SIGPIPE while writing to + * a coprocess. AT&T ksh doesn't seem to do this (seems + * to just check that the co-process is alive which is + * not enough). + */ + if (coproc.write >= 0 && coproc.write == fd) { + flags |= PO_COPROC; + opipe = block_pipe(); + } + for (s = Xstring(xs, xp); len > 0; ) { + if ((c = write(fd, s, len)) < 0) { + if (flags & PO_COPROC) + restore_pipe(opipe); + if (errno == EINTR) { + /* allow user to ^C out */ + intrcheck(); + if (flags & PO_COPROC) + opipe = block_pipe(); + continue; + } + return (1); + } + s += c; + len -= c; + } + if (flags & PO_COPROC) + restore_pipe(opipe); + } + + return (0); +} + +static int +s_get(void) +{ + return (*s_ptr++); +} + +static void +s_put(int c MKSH_A_UNUSED) +{ + --s_ptr; +} + +int +c_whence(const char **wp) +{ + struct tbl *tp; + const char *id; + bool pflag = false, vflag = false, Vflag = false; + int rv = 0, optc, fcflags; + bool iam_whence = wp[0][0] == 'w'; + const char *opts = iam_whence ? "pv" : "pvV"; + + while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) + switch (optc) { + case 'p': + pflag = true; + break; + case 'v': + vflag = true; + break; + case 'V': + Vflag = true; + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + + fcflags = FC_BI | FC_PATH | FC_FUNC; + if (!iam_whence) { + /* Note that -p on its own is deal with in comexec() */ + if (pflag) + fcflags |= FC_DEFPATH; + /* Convert command options to whence options - note that + * command -pV uses a different path search than whence -v + * or whence -pv. This should be considered a feature. + */ + vflag = Vflag; + } + if (pflag) + fcflags &= ~(FC_BI | FC_FUNC); + + while ((vflag || rv == 0) && (id = *wp++) != NULL) { + uint32_t h = 0; + + tp = NULL; + if ((iam_whence || vflag) && !pflag) + tp = ktsearch(&keywords, id, h = hash(id)); + if (!tp && !pflag) { + tp = ktsearch(&aliases, id, h ? h : hash(id)); + if (tp && !(tp->flag & ISSET)) + tp = NULL; + } + if (!tp) + tp = findcom(id, fcflags); + if (vflag || (tp->type != CALIAS && tp->type != CEXEC && + tp->type != CTALIAS)) + shf_puts(id, shl_stdout); + switch (tp->type) { + case CKEYWD: + if (vflag) + shf_puts(" is a reserved word", shl_stdout); + break; + case CALIAS: + if (vflag) + shprintf(" is an %salias for ", + (tp->flag & EXPORT) ? "exported " : null); + if (!iam_whence && !vflag) + shprintf("alias %s=", id); + print_value_quoted(tp->val.s); + break; + case CFUNC: + if (vflag) { + shf_puts(" is a", shl_stdout); + if (tp->flag & EXPORT) + shf_puts("n exported", shl_stdout); + if (tp->flag & TRACE) + shf_puts(" traced", shl_stdout); + if (!(tp->flag & ISSET)) { + shf_puts(" undefined", shl_stdout); + if (tp->u.fpath) + shprintf(" (autoload from %s)", + tp->u.fpath); + } + shf_puts(" function", shl_stdout); + } + break; + case CSHELL: + if (vflag) + shprintf(" is a%s shell builtin", + (tp->flag & SPEC_BI) ? " special" : null); + break; + case CTALIAS: + case CEXEC: + if (tp->flag & ISSET) { + if (vflag) { + shf_puts(" is ", shl_stdout); + if (tp->type == CTALIAS) + shprintf("a tracked %salias for ", + (tp->flag & EXPORT) ? + "exported " : null); + } + shf_puts(tp->val.s, shl_stdout); + } else { + if (vflag) + shf_puts(" not found", shl_stdout); + rv = 1; + } + break; + default: + shprintf("%s is *GOK*", id); + break; + } + if (vflag || !rv) + shf_putc('\n', shl_stdout); + } + return (rv); +} + +/* Deal with command -vV - command -p dealt with in comexec() */ +int +c_command(const char **wp) +{ + /* Let c_whence do the work. Note that c_command() must be + * a distinct function from c_whence() (tested in comexec()). + */ + return (c_whence(wp)); +} + +/* typeset, export, and readonly */ +int +c_typeset(const char **wp) +{ + struct block *l; + struct tbl *vp, **p; + Tflag fset = 0, fclr = 0, flag; + int thing = 0, field, base, optc; + const char *opts; + const char *fieldstr, *basestr; + bool localv = false, func = false, pflag = false, istset = true; + + switch (**wp) { + case 'e': /* export */ + fset |= EXPORT; + istset = false; + break; + case 'r': /* readonly */ + fset |= RDONLY; + istset = false; + break; + case 's': /* set */ + /* called with 'typeset -' */ + break; + case 't': /* typeset */ + localv = true; + break; + } + + /* see comment below regarding possible opions */ + opts = istset ? "L#R#UZ#afi#lnprtux" : "p"; + + fieldstr = basestr = NULL; + builtin_opt.flags |= GF_PLUSOPT; + /* AT&T ksh seems to have 0-9 as options which are multiplied + * to get a number that is used with -L, -R, -Z or -i (eg, -1R2 + * sets right justify in a field of 12). This allows options + * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and + * does not allow the number to be specified as a separate argument + * Here, the number must follow the RLZi option, but is optional + * (see the # kludge in ksh_getopt()). + */ + while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) { + flag = 0; + switch (optc) { + case 'L': + flag = LJUST; + fieldstr = builtin_opt.optarg; + break; + case 'R': + flag = RJUST; + fieldstr = builtin_opt.optarg; + break; + case 'U': + /* AT&T ksh uses u, but this conflicts with + * upper/lower case. If this option is changed, + * need to change the -U below as well + */ + flag = INT_U; + break; + case 'Z': + flag = ZEROFIL; + fieldstr = builtin_opt.optarg; + break; + case 'a': + /* + * this is supposed to set (-a) or unset (+a) the + * indexed array attribute; it does nothing on an + * existing regular string or indexed array though + */ + break; + case 'f': + func = true; + break; + case 'i': + flag = INTEGER; + basestr = builtin_opt.optarg; + break; + case 'l': + flag = LCASEV; + break; + case 'n': + set_refflag = (builtin_opt.info & GI_PLUS) ? 2 : 1; + break; + case 'p': + /* export, readonly: POSIX -p flag */ + /* typeset: show values as well */ + pflag = true; + if (istset) + continue; + break; + case 'r': + flag = RDONLY; + break; + case 't': + flag = TRACE; + break; + case 'u': + flag = UCASEV_AL; /* upper case / autoload */ + break; + case 'x': + flag = EXPORT; + break; + case '?': + return (1); + } + if (builtin_opt.info & GI_PLUS) { + fclr |= flag; + fset &= ~flag; + thing = '+'; + } else { + fset |= flag; + fclr &= ~flag; + thing = '-'; + } + } + + field = 0; + if (fieldstr && !bi_getn(fieldstr, &field)) + return (1); + base = 0; + if (basestr && !bi_getn(basestr, &base)) + return (1); + + if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] && + (wp[builtin_opt.optind][0] == '-' || + wp[builtin_opt.optind][0] == '+') && + wp[builtin_opt.optind][1] == '\0') { + thing = wp[builtin_opt.optind][0]; + builtin_opt.optind++; + } + + if (func && (((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT)) || set_refflag)) { + bi_errorf("only -t, -u and -x options may be used with -f"); + set_refflag = 0; + return (1); + } + if (wp[builtin_opt.optind]) { + /* Take care of exclusions. + * At this point, flags in fset are cleared in fclr and vice + * versa. This property should be preserved. + */ + if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ + fset &= ~UCASEV_AL; + if (fset & LJUST) /* LJUST has priority over RJUST */ + fset &= ~RJUST; + if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */ + fset |= RJUST; + fclr &= ~RJUST; + } + /* Setting these attributes clears the others, unless they + * are also set in this command + */ + if ((fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | + INTEGER | INT_U | INT_L)) || set_refflag) + fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | + LCASEV | INTEGER | INT_U | INT_L); + } + + /* set variables and attributes */ + if (wp[builtin_opt.optind]) { + int i, rv = 0; + struct tbl *f; + + if (localv && !func) + fset |= LOCAL; + for (i = builtin_opt.optind; wp[i]; i++) { + if (func) { + f = findfunc(wp[i], hash(wp[i]), + (fset&UCASEV_AL) ? true : false); + if (!f) { + /* AT&T ksh does ++rv: bogus */ + rv = 1; + continue; + } + if (fset | fclr) { + f->flag |= fset; + f->flag &= ~fclr; + } else + fptreef(shl_stdout, 0, + f->flag & FKSH ? + "function %s %T\n" : + "%s() %T\n", wp[i], f->val.t); + } else if (!typeset(wp[i], fset, fclr, field, base)) { + bi_errorf("%s: not identifier", wp[i]); + set_refflag = 0; + return (1); + } + } + set_refflag = 0; + return (rv); + } + + /* list variables and attributes */ + flag = fset | fclr; /* no difference at this point.. */ + if (func) { + for (l = e->loc; l; l = l->next) { + for (p = ktsort(&l->funs); (vp = *p++); ) { + if (flag && (vp->flag & flag) == 0) + continue; + if (thing == '-') + fptreef(shl_stdout, 0, vp->flag & FKSH ? + "function %s %T\n" : "%s() %T\n", + vp->name, vp->val.t); + else + shprintf("%s\n", vp->name); + } + } + } else { + for (l = e->loc; l; l = l->next) { + for (p = ktsort(&l->vars); (vp = *p++); ) { + struct tbl *tvp; + bool any_set = false; + /* + * See if the parameter is set (for arrays, if any + * element is set). + */ + for (tvp = vp; tvp; tvp = tvp->u.array) + if (tvp->flag & ISSET) { + any_set = true; + break; + } + + /* + * Check attributes - note that all array elements + * have (should have?) the same attributes, so checking + * the first is sufficient. + * + * Report an unset param only if the user has + * explicitly given it some attribute (like export); + * otherwise, after "echo $FOO", we would report FOO... + */ + if (!any_set && !(vp->flag & USERATTRIB)) + continue; + if (flag && (vp->flag & flag) == 0) + continue; + for (; vp; vp = vp->u.array) { + /* Ignore array elements that aren't + * set unless there are no set elements, + * in which case the first is reported on */ + if ((vp->flag&ARRAY) && any_set && + !(vp->flag & ISSET)) + continue; + /* no arguments */ + if (thing == 0 && flag == 0) { + /* AT&T ksh prints things + * like export, integer, + * leftadj, zerofill, etc., + * but POSIX says must + * be suitable for re-entry... + */ + shf_puts("typeset ", shl_stdout); + if (((vp->flag&(ARRAY|ASSOC))==ASSOC)) + shf_puts("-n ", shl_stdout); + if ((vp->flag&INTEGER)) + shf_puts("-i ", shl_stdout); + if ((vp->flag&EXPORT)) + shf_puts("-x ", shl_stdout); + if ((vp->flag&RDONLY)) + shf_puts("-r ", shl_stdout); + if ((vp->flag&TRACE)) + shf_puts("-t ", shl_stdout); + if ((vp->flag&LJUST)) + shprintf("-L%d ", vp->u2.field); + if ((vp->flag&RJUST)) + shprintf("-R%d ", vp->u2.field); + if ((vp->flag&ZEROFIL)) + shf_puts("-Z ", shl_stdout); + if ((vp->flag&LCASEV)) + shf_puts("-l ", shl_stdout); + if ((vp->flag&UCASEV_AL)) + shf_puts("-u ", shl_stdout); + if ((vp->flag&INT_U)) + shf_puts("-U ", shl_stdout); + shf_puts(vp->name, shl_stdout); + if (pflag) { + char *s = str_val(vp); + + shf_putc('=', shl_stdout); + /* AT&T ksh can't have + * justified integers.. */ + if ((vp->flag & + (INTEGER|LJUST|RJUST)) == + INTEGER) + shf_puts(s, shl_stdout); + else + print_value_quoted(s); + } + shf_putc('\n', shl_stdout); + if (vp->flag & ARRAY) + break; + } else { + if (pflag) + shf_puts(istset ? + "typeset " : + (flag & EXPORT) ? + "export " : + "readonly ", + shl_stdout); + if ((vp->flag&ARRAY) && any_set) + shprintf("%s[%lu]", + vp->name, + arrayindex(vp)); + else + shf_puts(vp->name, shl_stdout); + if (thing == '-' && (vp->flag&ISSET)) { + char *s = str_val(vp); + + shf_putc('=', shl_stdout); + /* AT&T ksh can't have + * justified integers.. */ + if ((vp->flag & + (INTEGER|LJUST|RJUST)) == + INTEGER) + shf_puts(s, shl_stdout); + else + print_value_quoted(s); + } + shf_putc('\n', shl_stdout); + } + /* Only report first 'element' of an array with + * no set elements. + */ + if (!any_set) + break; + } + } + } + } + return (0); +} + +int +c_alias(const char **wp) +{ + struct table *t = &aliases; + int rv = 0, prefix = 0; + bool rflag = false, tflag, Uflag = false, pflag = false; + Tflag xflag = 0; + int optc; + + builtin_opt.flags |= GF_PLUSOPT; + while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) { + prefix = builtin_opt.info & GI_PLUS ? '+' : '-'; + switch (optc) { + case 'd': +#ifdef MKSH_NOPWNAM + t = NULL; /* fix "alias -dt" */ +#else + t = &homedirs; +#endif + break; + case 'p': + pflag = true; + break; + case 'r': + rflag = true; + break; + case 't': + t = &taliases; + break; + case 'U': + /* + * kludge for tracked alias initialization + * (don't do a path search, just make an entry) + */ + Uflag = true; + break; + case 'x': + xflag = EXPORT; + break; + case '?': + return (1); + } + } +#ifdef MKSH_NOPWNAM + if (t == NULL) + return (0); +#endif + wp += builtin_opt.optind; + + if (!(builtin_opt.info & GI_MINUSMINUS) && *wp && + (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') { + prefix = wp[0][0]; + wp++; + } + + tflag = t == &taliases; + + /* "hash -r" means reset all the tracked aliases.. */ + if (rflag) { + static const char *args[] = { + "unalias", "-ta", NULL + }; + + if (!tflag || *wp) { + shf_puts("alias: -r flag can only be used with -t" + " and without arguments\n", shl_stdout); + return (1); + } + ksh_getopt_reset(&builtin_opt, GF_ERROR); + return (c_unalias(args)); + } + + if (*wp == NULL) { + struct tbl *ap, **p; + + for (p = ktsort(t); (ap = *p++) != NULL; ) + if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) { + if (pflag) + shf_puts("alias ", shl_stdout); + shf_puts(ap->name, shl_stdout); + if (prefix != '+') { + shf_putc('=', shl_stdout); + print_value_quoted(ap->val.s); + } + shf_putc('\n', shl_stdout); + } + } + + for (; *wp != NULL; wp++) { + const char *alias = *wp, *val, *newval; + char *xalias = NULL; + struct tbl *ap; + uint32_t h; + + if ((val = cstrchr(alias, '='))) { + strndupx(xalias, alias, val++ - alias, ATEMP); + alias = xalias; + } + h = hash(alias); + if (val == NULL && !tflag && !xflag) { + ap = ktsearch(t, alias, h); + if (ap != NULL && (ap->flag&ISSET)) { + if (pflag) + shf_puts("alias ", shl_stdout); + shf_puts(ap->name, shl_stdout); + if (prefix != '+') { + shf_putc('=', shl_stdout); + print_value_quoted(ap->val.s); + } + shf_putc('\n', shl_stdout); + } else { + shprintf("%s alias not found\n", alias); + rv = 1; + } + continue; + } + ap = ktenter(t, alias, h); + ap->type = tflag ? CTALIAS : CALIAS; + /* Are we setting the value or just some flags? */ + if ((val && !tflag) || (!val && tflag && !Uflag)) { + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + /* ignore values for -t (AT&T ksh does this) */ + newval = tflag ? search(alias, path, X_OK, NULL) : val; + if (newval) { + strdupx(ap->val.s, newval, APERM); + ap->flag |= ALLOC|ISSET; + } else + ap->flag &= ~ISSET; + } + ap->flag |= DEFINED; + if (prefix == '+') + ap->flag &= ~xflag; + else + ap->flag |= xflag; + afree(xalias, ATEMP); + } + + return (rv); +} + +int +c_unalias(const char **wp) +{ + struct table *t = &aliases; + struct tbl *ap; + int optc, rv = 0; + bool all = false; + + while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1) + switch (optc) { + case 'a': + all = true; + break; + case 'd': +#ifdef MKSH_NOPWNAM + t = NULL; /* fix "unalias -dt" */ +#else + t = &homedirs; +#endif + break; + case 't': + t = &taliases; + break; + case '?': + return (1); + } +#ifdef MKSH_NOPWNAM + if (t == NULL) + return (0); +#endif + wp += builtin_opt.optind; + + for (; *wp != NULL; wp++) { + ap = ktsearch(t, *wp, hash(*wp)); + if (ap == NULL) { + rv = 1; /* POSIX */ + continue; + } + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + ap->flag &= ~(DEFINED|ISSET|EXPORT); + } + + if (all) { + struct tstate ts; + + for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) { + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + ap->flag &= ~(DEFINED|ISSET|EXPORT); + } + } + + return (rv); +} + +int +c_let(const char **wp) +{ + int rv = 1; + mksh_ari_t val; + + if (wp[1] == NULL) /* AT&T ksh does this */ + bi_errorf("no arguments"); + else + for (wp++; *wp; wp++) + if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) { + rv = 2; /* distinguish error from zero result */ + break; + } else + rv = val == 0; + return (rv); +} + +int +c_jobs(const char **wp) +{ + int optc, flag = 0, nflag = 0, rv = 0; + + while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1) + switch (optc) { + case 'l': + flag = 1; + break; + case 'p': + flag = 2; + break; + case 'n': + nflag = 1; + break; + case 'z': /* debugging: print zombies */ + nflag = -1; + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + if (!*wp) { + if (j_jobs(NULL, flag, nflag)) + rv = 1; + } else { + for (; *wp; wp++) + if (j_jobs(*wp, flag, nflag)) + rv = 1; + } + return (rv); +} + +#ifndef MKSH_UNEMPLOYED +int +c_fgbg(const char **wp) +{ + bool bg = strcmp(*wp, "bg") == 0; + int rv = 0; + + if (!Flag(FMONITOR)) { + bi_errorf("job control not enabled"); + return (1); + } + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + wp += builtin_opt.optind; + if (*wp) + for (; *wp; wp++) + rv = j_resume(*wp, bg); + else + rv = j_resume("%%", bg); + return (bg ? 0 : rv); +} +#endif + +/* format a single kill item */ +static char * +kill_fmt_entry(char *buf, int buflen, int i, const void *arg) +{ + const struct kill_info *ki = (const struct kill_info *)arg; + + i++; + shf_snprintf(buf, buflen, "%*d %*s %s", + ki->num_width, i, + ki->name_width, sigtraps[i].name, + sigtraps[i].mess); + return (buf); +} + +int +c_kill(const char **wp) +{ + Trap *t = NULL; + const char *p; + bool lflag = false; + int i, n, rv, sig; + + /* assume old style options if -digits or -UPPERCASE */ + if ((p = wp[1]) && *p == '-' && (ksh_isdigit(p[1]) || + ksh_isupper(p[1]))) { + if (!(t = gettrap(p + 1, true))) { + bi_errorf("bad signal '%s'", p + 1); + return (1); + } + i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2; + } else { + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1) + switch (optc) { + case 'l': + lflag = true; + break; + case 's': + if (!(t = gettrap(builtin_opt.optarg, true))) { + bi_errorf("bad signal '%s'", + builtin_opt.optarg); + return (1); + } + break; + case '?': + return (1); + } + i = builtin_opt.optind; + } + if ((lflag && t) || (!wp[i] && !lflag)) { +#ifndef MKSH_SMALL + shf_puts("usage:\tkill [-s signame | -signum | -signame]" + " { job | pid | pgrp } ...\n" + "\tkill -l [exit_status ...]\n", shl_out); +#endif + bi_errorfz(); + return (1); + } + + if (lflag) { + if (wp[i]) { + for (; wp[i]; i++) { + if (!bi_getn(wp[i], &n)) + return (1); + if (n > 128 && n < 128 + NSIG) + n -= 128; + if (n > 0 && n < NSIG) + shprintf("%s\n", sigtraps[n].name); + else + shprintf("%d\n", n); + } + } else { + int w, j, mess_cols, mess_octs; + struct kill_info ki; + + for (j = NSIG, ki.num_width = 1; j >= 10; j /= 10) + ki.num_width++; + ki.name_width = mess_cols = mess_octs = 0; + for (j = 0; j < NSIG; j++) { + w = strlen(sigtraps[j].name); + if (w > ki.name_width) + ki.name_width = w; + w = strlen(sigtraps[j].mess); + if (w > mess_octs) + mess_octs = w; + w = utf_mbswidth(sigtraps[j].mess); + if (w > mess_cols) + mess_cols = w; + } + + print_columns(shl_stdout, NSIG - 1, + kill_fmt_entry, (void *)&ki, + ki.num_width + 1 + ki.name_width + 1 + mess_octs, + ki.num_width + 1 + ki.name_width + 1 + mess_cols, + true); + } + return (0); + } + rv = 0; + sig = t ? t->signal : SIGTERM; + for (; (p = wp[i]); i++) { + if (*p == '%') { + if (j_kill(p, sig)) + rv = 1; + } else if (!getn(p, &n)) { + bi_errorf("%s: arguments must be jobs or process IDs", + p); + rv = 1; + } else { + if (mksh_kill(n, sig) < 0) { + bi_errorf("%s: %s", p, strerror(errno)); + rv = 1; + } + } + } + return (rv); +} + +void +getopts_reset(int val) +{ + if (val >= 1) { + ksh_getopt_reset(&user_opt, GF_NONAME | GF_PLUSOPT); + user_opt.optind = user_opt.uoptind = val; + } +} + +int +c_getopts(const char **wp) +{ + int argc, optc, rv; + const char *opts, *var; + char buf[3]; + struct tbl *vq, *voptarg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + wp += builtin_opt.optind; + + opts = *wp++; + if (!opts) { + bi_errorf("missing options argument"); + return (1); + } + + var = *wp++; + if (!var) { + bi_errorf("missing name argument"); + return (1); + } + if (!*var || *skip_varname(var, true)) { + bi_errorf("%s: is not an identifier", var); + return (1); + } + + if (e->loc->next == NULL) { + internal_warningf("c_getopts: no argv"); + return (1); + } + /* Which arguments are we parsing... */ + if (*wp == NULL) + wp = e->loc->next->argv; + else + *--wp = e->loc->next->argv[0]; + + /* Check that our saved state won't cause a core dump... */ + for (argc = 0; wp[argc]; argc++) + ; + if (user_opt.optind > argc || + (user_opt.p != 0 && + user_opt.p > strlen(wp[user_opt.optind - 1]))) { + bi_errorf("arguments changed since last call"); + return (1); + } + + user_opt.optarg = NULL; + optc = ksh_getopt(wp, &user_opt, opts); + + if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) { + buf[0] = '+'; + buf[1] = optc; + buf[2] = '\0'; + } else { + /* POSIX says var is set to ? at end-of-options, AT&T ksh + * sets it to null - we go with POSIX... + */ + buf[0] = optc < 0 ? '?' : optc; + buf[1] = '\0'; + } + + /* AT&T ksh93 in fact does change OPTIND for unknown options too */ + user_opt.uoptind = user_opt.optind; + + voptarg = global("OPTARG"); + voptarg->flag &= ~RDONLY; /* AT&T ksh clears ro and int */ + /* Paranoia: ensure no bizarre results. */ + if (voptarg->flag & INTEGER) + typeset("OPTARG", 0, INTEGER, 0, 0); + if (user_opt.optarg == NULL) + unset(voptarg, 1); + else + /* This can't fail (have cleared readonly/integer) */ + setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR); + + rv = 0; + + vq = global(var); + /* Error message already printed (integer, readonly) */ + if (!setstr(vq, buf, KSH_RETURN_ERROR)) + rv = 1; + if (Flag(FEXPORT)) + typeset(var, EXPORT, 0, 0, 0); + + return (optc < 0 ? 1 : rv); +} + +int +c_bind(const char **wp) +{ + int optc, rv = 0; +#ifndef MKSH_SMALL + bool macro = false; +#endif + bool list = false; + const char *cp; + char *up; + + while ((optc = ksh_getopt(wp, &builtin_opt, +#ifndef MKSH_SMALL + "lm" +#else + "l" +#endif + )) != -1) + switch (optc) { + case 'l': + list = true; + break; +#ifndef MKSH_SMALL + case 'm': + macro = true; + break; +#endif + case '?': + return (1); + } + wp += builtin_opt.optind; + + if (*wp == NULL) /* list all */ + rv = x_bind(NULL, NULL, +#ifndef MKSH_SMALL + false, +#endif + list); + + for (; *wp != NULL; wp++) { + if ((cp = cstrchr(*wp, '=')) == NULL) + up = NULL; + else { + strdupx(up, *wp, ATEMP); + up[cp++ - *wp] = '\0'; + } + if (x_bind(up ? up : *wp, cp, +#ifndef MKSH_SMALL + macro, +#endif + false)) + rv = 1; + afree(up, ATEMP); + } + + return (rv); +} + +/* :, false and true (and ulimit if MKSH_NO_LIMITS) */ +int +c_label(const char **wp) +{ + return (wp[0][0] == 'f' ? 1 : 0); +} + +int +c_shift(const char **wp) +{ + struct block *l = e->loc; + int n; + mksh_ari_t val; + const char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + arg = wp[builtin_opt.optind]; + + if (arg) { + evaluate(arg, &val, KSH_UNWIND_ERROR, false); + n = val; + } else + n = 1; + if (n < 0) { + bi_errorf("%s: bad number", arg); + return (1); + } + if (l->argc < n) { + bi_errorf("nothing to shift"); + return (1); + } + l->argv[n] = l->argv[0]; + l->argv += n; + l->argc -= n; + return (0); +} + +int +c_umask(const char **wp) +{ + int i, optc; + const char *cp; + bool symbolic = false; + mode_t old_umask; + + while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1) + switch (optc) { + case 'S': + symbolic = true; + break; + case '?': + return (1); + } + cp = wp[builtin_opt.optind]; + if (cp == NULL) { + old_umask = umask((mode_t)0); + umask(old_umask); + if (symbolic) { + char buf[18], *p; + int j; + + old_umask = ~old_umask; + p = buf; + for (i = 0; i < 3; i++) { + *p++ = "ugo"[i]; + *p++ = '='; + for (j = 0; j < 3; j++) + if (old_umask & (1 << (8 - (3*i + j)))) + *p++ = "rwx"[j]; + *p++ = ','; + } + p[-1] = '\0'; + shprintf("%s\n", buf); + } else + shprintf("%#3.3o\n", (unsigned int)old_umask); + } else { + mode_t new_umask; + + if (ksh_isdigit(*cp)) { + for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++) + new_umask = new_umask * 8 + (*cp - '0'); + if (*cp) { + bi_errorf("bad number"); + return (1); + } + } else { + /* symbolic format */ + int positions, new_val; + char op; + + old_umask = umask((mode_t)0); + umask(old_umask); /* in case of error */ + old_umask = ~old_umask; + new_umask = old_umask; + positions = 0; + while (*cp) { + while (*cp && vstrchr("augo", *cp)) + switch (*cp++) { + case 'a': + positions |= 0111; + break; + case 'u': + positions |= 0100; + break; + case 'g': + positions |= 0010; + break; + case 'o': + positions |= 0001; + break; + } + if (!positions) + positions = 0111; /* default is a */ + if (!vstrchr("=+-", op = *cp)) + break; + cp++; + new_val = 0; + while (*cp && vstrchr("rwxugoXs", *cp)) + switch (*cp++) { + case 'r': new_val |= 04; break; + case 'w': new_val |= 02; break; + case 'x': new_val |= 01; break; + case 'u': + new_val |= old_umask >> 6; + break; + case 'g': + new_val |= old_umask >> 3; + break; + case 'o': + new_val |= old_umask >> 0; + break; + case 'X': + if (old_umask & 0111) + new_val |= 01; + break; + case 's': + /* ignored */ + break; + } + new_val = (new_val & 07) * positions; + switch (op) { + case '-': + new_umask &= ~new_val; + break; + case '=': + new_umask = new_val | + (new_umask & ~(positions * 07)); + break; + case '+': + new_umask |= new_val; + } + if (*cp == ',') { + positions = 0; + cp++; + } else if (!vstrchr("=+-", *cp)) + break; + } + if (*cp) { + bi_errorf("bad mask"); + return (1); + } + new_umask = ~new_umask; + } + umask(new_umask); + } + return (0); +} + +int +c_dot(const char **wp) +{ + const char *file, *cp, **argv; + int argc, i, errcode; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + + if ((cp = wp[builtin_opt.optind]) == NULL) { + bi_errorf("missing argument"); + return (1); + } + if ((file = search(cp, path, R_OK, &errcode)) == NULL) { + bi_errorf("%s: %s", cp, + errcode ? strerror(errcode) : "not found"); + return (1); + } + + /* Set positional parameters? */ + if (wp[builtin_opt.optind + 1]) { + argv = wp + builtin_opt.optind; + argv[0] = e->loc->argv[0]; /* preserve $0 */ + for (argc = 0; argv[argc + 1]; argc++) + ; + } else { + argc = 0; + argv = NULL; + } + if ((i = include(file, argc, argv, 0)) < 0) { + /* should not happen */ + bi_errorf("%s: %s", cp, strerror(errno)); + return (1); + } + return (i); +} + +int +c_wait(const char **wp) +{ + int rv = 0, sig; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + wp += builtin_opt.optind; + if (*wp == NULL) { + while (waitfor(NULL, &sig) >= 0) + ; + rv = sig; + } else { + for (; *wp; wp++) + rv = waitfor(*wp, &sig); + if (rv < 0) + rv = sig ? sig : 127; /* magic exit code: bad job-id */ + } + return (rv); +} + +int +c_read(const char **wp) +{ + int c = 0, ecode = 0, fd = 0, optc; + bool expande = true, historyr = false, expanding; + const char *cp, *emsg; + struct shf *shf; + XString cs, xs = { NULL, NULL, 0, NULL}; + struct tbl *vp; + char *ccp, *xp = NULL, *wpalloc = NULL; + static char REPLY[] = "REPLY"; + + while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1) + switch (optc) { + case 'p': + if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return (1); + } + break; + case 'r': + expande = false; + break; + case 's': + historyr = true; + break; + case 'u': + if (!*(cp = builtin_opt.optarg)) + fd = 0; + else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) { + bi_errorf("-u: %s: %s", cp, emsg); + return (1); + } + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + + if (*wp == NULL) + *--wp = REPLY; + + /* Since we can't necessarily seek backwards on non-regular files, + * don't buffer them so we can't read too much. + */ + shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare); + + if ((cp = cstrchr(*wp, '?')) != NULL) { + strdupx(wpalloc, *wp, ATEMP); + wpalloc[cp - *wp] = '\0'; + *wp = wpalloc; + if (isatty(fd)) { + /* AT&T ksh says it prints prompt on fd if it's open + * for writing and is a tty, but it doesn't do it + * (it also doesn't check the interactive flag, + * as is indicated in the Kornshell book). + */ + shellf("%s", cp+1); + } + } + + /* If we are reading from the co-process for the first time, + * make sure the other side of the pipe is closed first. This allows + * the detection of eof. + * + * This is not compatible with AT&T ksh... the fd is kept so another + * coproc can be started with same output, however, this means eof + * can't be detected... This is why it is closed here. + * If this call is removed, remove the eof check below, too. + * coproc_readw_close(fd); + */ + + if (historyr) + Xinit(xs, xp, 128, ATEMP); + expanding = false; + Xinit(cs, ccp, 128, ATEMP); + for (; *wp != NULL; wp++) { + for (ccp = Xstring(cs, ccp); ; ) { + if (c == '\n' || c == EOF) + break; + while (1) { + c = shf_getc(shf); + if (c == '\0') + continue; + if (c == EOF && shf_error(shf) && + shf_errno(shf) == EINTR) { + /* Was the offending signal one that + * would normally kill a process? + * If so, pretend the read was killed. + */ + ecode = fatal_trap_check(); + + /* non fatal (eg, CHLD), carry on */ + if (!ecode) { + shf_clearerr(shf); + continue; + } + } + break; + } + if (historyr) { + Xcheck(xs, xp); + Xput(xs, xp, c); + } + Xcheck(cs, ccp); + if (expanding) { + expanding = false; + if (c == '\n') { + c = 0; + if (Flag(FTALKING_I) && isatty(fd)) { + /* set prompt in case this is + * called from .profile or $ENV + */ + set_prompt(PS2, NULL); + pprompt(prompt, 0); + } + } else if (c != EOF) + Xput(cs, ccp, c); + continue; + } + if (expande && c == '\\') { + expanding = true; + continue; + } + if (c == '\n' || c == EOF) + break; + if (ctype(c, C_IFS)) { + if (Xlength(cs, ccp) == 0 && ctype(c, C_IFSWS)) + continue; + if (wp[1]) + break; + } + Xput(cs, ccp, c); + } + /* strip trailing IFS white space from last variable */ + if (!wp[1]) + while (Xlength(cs, ccp) && ctype(ccp[-1], C_IFS) && + ctype(ccp[-1], C_IFSWS)) + ccp--; + Xput(cs, ccp, '\0'); + vp = global(*wp); + /* Must be done before setting export. */ + if (vp->flag & RDONLY) { + shf_flush(shf); + bi_errorf("%s is read only", *wp); + afree(wpalloc, ATEMP); + return (1); + } + if (Flag(FEXPORT)) + typeset(*wp, EXPORT, 0, 0, 0); + if (!setstr(vp, Xstring(cs, ccp), KSH_RETURN_ERROR)) { + shf_flush(shf); + afree(wpalloc, ATEMP); + return (1); + } + } + + shf_flush(shf); + if (historyr) { + Xput(xs, xp, '\0'); + histsave(&source->line, Xstring(xs, xp), true, false); + Xfree(xs, xp); + } + /* if this is the co-process fd, close the file descriptor + * (can get eof if and only if all processes are have died, ie, + * coproc.njobs is 0 and the pipe is closed). + */ + if (c == EOF && !ecode) + coproc_read_close(fd); + + afree(wpalloc, ATEMP); + return (ecode ? ecode : c == EOF); +} + +int +c_eval(const char **wp) +{ + struct source *s, *saves = source; + unsigned char savef; + int rv; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + s = pushs(SWORDS, ATEMP); + s->u.strv = wp + builtin_opt.optind; + + /*- + * The following code handles the case where the command is + * empty due to failed command substitution, for example by + * eval "$(false)" + * This has historically returned 1 by AT&T ksh88. In this + * case, shell() will not set or change exstat because the + * compiled tree is empty, so it will use the value we pass + * from subst_exstat, which is cleared in execute(), so it + * should have been 0 if there were no substitutions. + * + * POSIX however says we don't do this, even though it is + * traditionally done. AT&T ksh93 agrees with POSIX, so we + * do. The following is an excerpt from SUSv4 [1003.2-2008]: + * + * 2.9.1: Simple Commands + * ... If there is a command name, execution shall + * continue as described in 2.9.1.1 [Command Search + * and Execution]. If there is no command name, but + * the command contained a command substitution, the + * command shall complete with the exit status of the + * last command substitution performed. + * 2.9.1.1: Command Search and Execution + * (1) a. If the command name matches the name of a + * special built-in utility, that special built-in + * utility shall be invoked. + * 2.14.5: eval + * If there are no arguments, or only null arguments, + * eval shall return a zero exit status; ... + */ + /* exstat = subst_exstat; */ /* AT&T ksh88 */ + exstat = 0; /* SUSv4 */ + + savef = Flag(FERREXIT); + Flag(FERREXIT) = 0; + rv = shell(s, false); + Flag(FERREXIT) = savef; + source = saves; + afree(s, ATEMP); + return (rv); +} + +int +c_trap(const char **wp) +{ + int i; + const char *s; + Trap *p; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + wp += builtin_opt.optind; + + if (*wp == NULL) { + for (p = sigtraps, i = NSIG+1; --i >= 0; p++) + if (p->trap != NULL) { + shf_puts("trap -- ", shl_stdout); + print_value_quoted(p->trap); + shprintf(" %s\n", p->name); + } + return (0); + } + + /* + * Use case sensitive lookup for first arg so the + * command 'exit' isn't confused with the pseudo-signal + * 'EXIT'. + */ + s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */ + if (s != NULL && s[0] == '-' && s[1] == '\0') + s = NULL; + + /* set/clear traps */ + while (*wp != NULL) { + p = gettrap(*wp++, true); + if (p == NULL) { + bi_errorf("bad signal %s", wp[-1]); + return (1); + } + settrap(p, s); + } + return (0); +} + +int +c_exitreturn(const char **wp) +{ + int n, how = LEXIT; + const char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + arg = wp[builtin_opt.optind]; + + if (arg) { + if (!getn(arg, &n)) { + exstat = 1; + warningf(true, "%s: bad number", arg); + } else + exstat = n; + } + if (wp[0][0] == 'r') { /* return */ + struct env *ep; + + /* need to tell if this is exit or return so trap exit will + * work right (POSIX) + */ + for (ep = e; ep; ep = ep->oenv) + if (STOP_RETURN(ep->type)) { + how = LRETURN; + break; + } + } + + if (how == LEXIT && !really_exit && j_stopped_running()) { + really_exit = 1; + how = LSHELL; + } + + quitenv(NULL); /* get rid of any i/o redirections */ + unwind(how); + /* NOTREACHED */ +} + +int +c_brkcont(const char **wp) +{ + int n, quit; + struct env *ep, *last_ep = NULL; + const char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return (1); + arg = wp[builtin_opt.optind]; + + if (!arg) + n = 1; + else if (!bi_getn(arg, &n)) + return (1); + quit = n; + if (quit <= 0) { + /* AT&T ksh does this for non-interactive shells only - weird */ + bi_errorf("%s: bad value", arg); + return (1); + } + + /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */ + for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv) + if (ep->type == E_LOOP) { + if (--quit == 0) + break; + ep->flags |= EF_BRKCONT_PASS; + last_ep = ep; + } + + if (quit) { + /* AT&T ksh doesn't print a message - just does what it + * can. We print a message 'cause it helps in debugging + * scripts, but don't generate an error (ie, keep going). + */ + if (n == quit) { + warningf(true, "%s: cannot %s", wp[0], wp[0]); + return (0); + } + /* POSIX says if n is too big, the last enclosing loop + * shall be used. Doesn't say to print an error but we + * do anyway 'cause the user messed up. + */ + if (last_ep) + last_ep->flags &= ~EF_BRKCONT_PASS; + warningf(true, "%s: can only %s %d level(s)", + wp[0], wp[0], n - quit); + } + + unwind(*wp[0] == 'b' ? LBREAK : LCONTIN); + /* NOTREACHED */ +} + +int +c_set(const char **wp) +{ + int argi; + bool setargs; + struct block *l = e->loc; + const char **owp; + + if (wp[1] == NULL) { + static const char *args[] = { "set", "-", NULL }; + return (c_typeset(args)); + } + + argi = parse_args(wp, OF_SET, &setargs); + if (argi < 0) + return (1); + /* set $# and $* */ + if (setargs) { + wp += argi - 1; + owp = wp; + wp[0] = l->argv[0]; /* save $0 */ + while (*++wp != NULL) + strdupx(*wp, *wp, &l->area); + l->argc = wp - owp - 1; + l->argv = alloc((l->argc + 2) * sizeof(char *), &l->area); + for (wp = l->argv; (*wp++ = *owp++) != NULL; ) + ; + } + /*- + * POSIX says set exit status is 0, but old scripts that use + * getopt(1) use the construct + * set -- $(getopt ab:c "$@") + * which assumes the exit value set will be that of the $() + * (subst_exstat is cleared in execute() so that it will be 0 + * if there are no command substitutions). + * Switched ksh (!posix !sh) to POSIX in mksh R39b. + */ + return (Flag(FSH) ? subst_exstat : 0); +} + +int +c_unset(const char **wp) +{ + const char *id; + int optc; + bool unset_var = true; + + while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1) + switch (optc) { + case 'f': + unset_var = false; + break; + case 'v': + unset_var = true; + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + for (; (id = *wp) != NULL; wp++) + if (unset_var) { /* unset variable */ + struct tbl *vp; + char *cp = NULL; + size_t n; + + n = strlen(id); + if (n > 3 && id[n-3] == '[' && id[n-2] == '*' && + id[n-1] == ']') { + strndupx(cp, id, n - 3, ATEMP); + id = cp; + optc = 3; + } else + optc = vstrchr(id, '[') ? 0 : 1; + + vp = global(id); + afree(cp, ATEMP); + + if ((vp->flag&RDONLY)) { + bi_errorf("%s is read only", vp->name); + return (1); + } + unset(vp, optc); + } else /* unset function */ + define(id, NULL); + return (0); +} + +static void +p_time(struct shf *shf, bool posix, long tv_sec, int tv_usec, int width, + const char *prefix, const char *suffix) +{ + tv_usec /= 10000; + if (posix) + shf_fprintf(shf, "%s%*ld.%02d%s", prefix, width, + tv_sec, tv_usec, suffix); + else + shf_fprintf(shf, "%s%*ldm%d.%02ds%s", prefix, width, + tv_sec / 60, (int)(tv_sec % 60), tv_usec, suffix); +} + +int +c_times(const char **wp MKSH_A_UNUSED) +{ + struct rusage usage; + + getrusage(RUSAGE_SELF, &usage); + p_time(shl_stdout, false, usage.ru_utime.tv_sec, + usage.ru_utime.tv_usec, 0, null, " "); + p_time(shl_stdout, false, usage.ru_stime.tv_sec, + usage.ru_stime.tv_usec, 0, null, "\n"); + + getrusage(RUSAGE_CHILDREN, &usage); + p_time(shl_stdout, false, usage.ru_utime.tv_sec, + usage.ru_utime.tv_usec, 0, null, " "); + p_time(shl_stdout, false, usage.ru_stime.tv_sec, + usage.ru_stime.tv_usec, 0, null, "\n"); + + return (0); +} + +/* + * time pipeline (really a statement, not a built-in command) + */ +int +timex(struct op *t, int f, volatile int *xerrok) +{ +#define TF_NOARGS BIT(0) +#define TF_NOREAL BIT(1) /* don't report real time */ +#define TF_POSIX BIT(2) /* report in POSIX format */ + int rv = 0, tf = 0; + struct rusage ru0, ru1, cru0, cru1; + struct timeval usrtime, systime, tv0, tv1; + + gettimeofday(&tv0, NULL); + getrusage(RUSAGE_SELF, &ru0); + getrusage(RUSAGE_CHILDREN, &cru0); + if (t->left) { + /* + * Two ways of getting cpu usage of a command: just use t0 + * and t1 (which will get cpu usage from other jobs that + * finish while we are executing t->left), or get the + * cpu usage of t->left. AT&T ksh does the former, while + * pdksh tries to do the later (the j_usrtime hack doesn't + * really work as it only counts the last job). + */ + timerclear(&j_usrtime); + timerclear(&j_systime); + rv = execute(t->left, f | XTIME, xerrok); + if (t->left->type == TCOM) + tf |= t->left->str[0]; + gettimeofday(&tv1, NULL); + getrusage(RUSAGE_SELF, &ru1); + getrusage(RUSAGE_CHILDREN, &cru1); + } else + tf = TF_NOARGS; + + if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */ + tf |= TF_NOREAL; + timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime); + timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime); + } else { + timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime); + timeradd(&usrtime, &j_usrtime, &usrtime); + timersub(&ru1.ru_stime, &ru0.ru_stime, &systime); + timeradd(&systime, &j_systime, &systime); + } + + if (!(tf & TF_NOREAL)) { + timersub(&tv1, &tv0, &tv1); + if (tf & TF_POSIX) + p_time(shl_out, true, tv1.tv_sec, tv1.tv_usec, + 5, "real ", "\n"); + else + p_time(shl_out, false, tv1.tv_sec, tv1.tv_usec, + 5, null, " real "); + } + if (tf & TF_POSIX) + p_time(shl_out, true, usrtime.tv_sec, usrtime.tv_usec, + 5, "user ", "\n"); + else + p_time(shl_out, false, usrtime.tv_sec, usrtime.tv_usec, + 5, null, " user "); + if (tf & TF_POSIX) + p_time(shl_out, true, systime.tv_sec, systime.tv_usec, + 5, "sys ", "\n"); + else + p_time(shl_out, false, systime.tv_sec, systime.tv_usec, + 5, null, " system\n"); + shf_flush(shl_out); + + return (rv); +} + +void +timex_hook(struct op *t, char **volatile *app) +{ + char **wp = *app; + int optc, i, j; + Getopt opt; + + ksh_getopt_reset(&opt, 0); + opt.optind = 0; /* start at the start */ + while ((optc = ksh_getopt((const char **)wp, &opt, ":p")) != -1) + switch (optc) { + case 'p': + t->str[0] |= TF_POSIX; + break; + case '?': + errorf("time: -%s unknown option", opt.optarg); + case ':': + errorf("time: -%s requires an argument", + opt.optarg); + } + /* Copy command words down over options. */ + if (opt.optind != 0) { + for (i = 0; i < opt.optind; i++) + afree(wp[i], ATEMP); + for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++) + ; + } + if (!wp[0]) + t->str[0] |= TF_NOARGS; + *app = wp; +} + +/* exec with no args - args case is taken care of in comexec() */ +int +c_exec(const char **wp MKSH_A_UNUSED) +{ + int i; + + /* make sure redirects stay in place */ + if (e->savefd != NULL) { + for (i = 0; i < NUFILE; i++) { + if (e->savefd[i] > 0) + close(e->savefd[i]); + /* + * keep all file descriptors > 2 private for ksh, + * but not for POSIX or legacy/kludge sh + */ + if (!Flag(FPOSIX) && !Flag(FSH) && i > 2 && + e->savefd[i]) + fcntl(i, F_SETFD, FD_CLOEXEC); + } + e->savefd = NULL; + } + return (0); +} + +#if HAVE_MKNOD +int +c_mknod(const char **wp) +{ + int argc, optc, rv = 0; + bool ismkfifo = false; + const char **argv; + void *set = NULL; + mode_t mode = 0, oldmode = 0; + + while ((optc = ksh_getopt(wp, &builtin_opt, "m:")) != -1) { + switch (optc) { + case 'm': + set = setmode(builtin_opt.optarg); + if (set == NULL) { + bi_errorf("invalid file mode"); + return (1); + } + mode = getmode(set, (mode_t)(DEFFILEMODE)); + free(set); + break; + default: + goto c_mknod_usage; + } + } + argv = &wp[builtin_opt.optind]; + if (argv[0] == NULL) + goto c_mknod_usage; + for (argc = 0; argv[argc]; argc++) + ; + if (argc == 2 && argv[1][0] == 'p') + ismkfifo = true; + else if (argc != 4 || (argv[1][0] != 'b' && argv[1][0] != 'c')) + goto c_mknod_usage; + + if (set != NULL) + oldmode = umask((mode_t)0); + else + mode = DEFFILEMODE; + + mode |= (argv[1][0] == 'b') ? S_IFBLK : + (argv[1][0] == 'c') ? S_IFCHR : 0; + + if (!ismkfifo) { + unsigned long majnum, minnum; + dev_t dv; + char *c; + + majnum = strtoul(argv[2], &c, 0); + if ((c == argv[2]) || (*c != '\0')) { + bi_errorf("non-numeric device major '%s'", argv[2]); + goto c_mknod_err; + } + minnum = strtoul(argv[3], &c, 0); + if ((c == argv[3]) || (*c != '\0')) { + bi_errorf("non-numeric device minor '%s'", argv[3]); + goto c_mknod_err; + } + dv = makedev(majnum, minnum); + if ((unsigned long)(major(dv)) != majnum) { + bi_errorf("device major too large: %lu", majnum); + goto c_mknod_err; + } + if ((unsigned long)(minor(dv)) != minnum) { + bi_errorf("device minor too large: %lu", minnum); + goto c_mknod_err; + } + if (mknod(argv[0], mode, dv)) + goto c_mknod_failed; + } else if (mkfifo(argv[0], mode)) { + c_mknod_failed: + bi_errorf("%s: %s", *wp, strerror(errno)); + c_mknod_err: + rv = 1; + } + + if (set) + umask(oldmode); + return (rv); + c_mknod_usage: + bi_errorf("usage: mknod [-m mode] name b|c major minor"); + bi_errorf("usage: mknod [-m mode] name p"); + return (1); +} +#endif + +/* dummy function, special case in comexec() */ +int +c_builtin(const char **wp MKSH_A_UNUSED) +{ + return (0); +} + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" nexpr ; + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + + unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"| + "-L"|"-h"|"-S"|"-H"; + + binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"| + "<"|">" # rules used for [[ .. ]] expressions + ; + operand ::= <any thing> +*/ + +#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ + +int +c_test(const char **wp) +{ + int argc, res; + Test_env te; + + te.flags = 0; + te.isa = ptest_isa; + te.getopnd = ptest_getopnd; + te.eval = test_eval; + te.error = ptest_error; + + for (argc = 0; wp[argc]; argc++) + ; + + if (strcmp(wp[0], "[") == 0) { + if (strcmp(wp[--argc], "]") != 0) { + bi_errorf("missing ]"); + return (T_ERR_EXIT); + } + } + + te.pos.wp = wp + 1; + te.wp_end = wp + argc; + + /* + * Handle the special cases from POSIX.2, section 4.62.4. + * Implementation of all the rules isn't necessary since + * our parser does the right thing for the omitted steps. + */ + if (argc <= 5) { + const char **owp = wp; + int invert = 0; + Test_op op; + const char *opnd1, *opnd2; + + while (--argc >= 0) { + if ((*te.isa)(&te, TM_END)) + return (!0); + if (argc == 3) { + opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); + if ((op = (*te.isa)(&te, TM_BINOP))) { + opnd2 = (*te.getopnd)(&te, op, 1); + res = (*te.eval)(&te, op, opnd1, + opnd2, 1); + if (te.flags & TEF_ERROR) + return (T_ERR_EXIT); + if (invert & 1) + res = !res; + return (!res); + } + /* back up to opnd1 */ + te.pos.wp--; + } + if (argc == 1) { + opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); + if (strcmp(opnd1, "-t") == 0) + break; + res = (*te.eval)(&te, TO_STNZE, opnd1, + NULL, 1); + if (invert & 1) + res = !res; + return (!res); + } + if ((*te.isa)(&te, TM_NOT)) { + invert++; + } else + break; + } + te.pos.wp = owp + 1; + } + + return (test_parse(&te)); +} + +/* + * Generic test routines. + */ + +Test_op +test_isop(Test_meta meta, const char *s) +{ + char sc1; + const struct t_op *tbl; + + tbl = meta == TM_UNOP ? u_ops : b_ops; + if (*s) { + sc1 = s[1]; + for (; tbl->op_text[0]; tbl++) + if (sc1 == tbl->op_text[1] && !strcmp(s, tbl->op_text)) + return (tbl->op_num); + } + return (TO_NONOP); +} + +int +test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, + bool do_eval) +{ + int i, s; + size_t k; + struct stat b1, b2; + mksh_ari_t v1, v2; + + if (!do_eval) + return (0); + + switch ((int)op) { + /* + * Unary Operators + */ + case TO_STNZE: /* -n */ + return (*opnd1 != '\0'); + case TO_STZER: /* -z */ + return (*opnd1 == '\0'); + case TO_OPTION: /* -o */ + if ((i = *opnd1) == '!' || i == '?') + opnd1++; + if ((k = option(opnd1)) == (size_t)-1) + return (0); + return (i == '?' ? 1 : i == '!' ? !Flag(k) : Flag(k)); + case TO_FILRD: /* -r */ + return (test_eaccess(opnd1, R_OK) == 0); + case TO_FILWR: /* -w */ + return (test_eaccess(opnd1, W_OK) == 0); + case TO_FILEX: /* -x */ + return (test_eaccess(opnd1, X_OK) == 0); + case TO_FILAXST: /* -a */ + case TO_FILEXST: /* -e */ + return (stat(opnd1, &b1) == 0); + case TO_FILREG: /* -r */ + return (stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode)); + case TO_FILID: /* -d */ + return (stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode)); + case TO_FILCDEV: /* -c */ + return (stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode)); + case TO_FILBDEV: /* -b */ + return (stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode)); + case TO_FILFIFO: /* -p */ + return (stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode)); + case TO_FILSYM: /* -h -L */ + return (lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode)); + case TO_FILSOCK: /* -S */ + return (stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode)); + case TO_FILCDF:/* -H HP context dependent files (directories) */ + return (0); + case TO_FILSETU: /* -u */ + return (stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISUID) == S_ISUID); + case TO_FILSETG: /* -g */ + return (stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISGID) == S_ISGID); + case TO_FILSTCK: /* -k */ +#ifdef S_ISVTX + return (stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISVTX) == S_ISVTX); +#else + return (0); +#endif + case TO_FILGZ: /* -s */ + return (stat(opnd1, &b1) == 0 && b1.st_size > 0L); + case TO_FILTT: /* -t */ + if (opnd1 && !bi_getn(opnd1, &i)) { + te->flags |= TEF_ERROR; + i = 0; + } else + i = isatty(opnd1 ? i : 0); + return (i); + case TO_FILUID: /* -O */ + return (stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid); + case TO_FILGID: /* -G */ + return (stat(opnd1, &b1) == 0 && b1.st_gid == getegid()); + /* + * Binary Operators + */ + case TO_STEQL: /* = */ + if (te->flags & TEF_DBRACKET) + return (gmatchx(opnd1, opnd2, false)); + return (strcmp(opnd1, opnd2) == 0); + case TO_STNEQ: /* != */ + if (te->flags & TEF_DBRACKET) + return (!gmatchx(opnd1, opnd2, false)); + return (strcmp(opnd1, opnd2) != 0); + case TO_STLT: /* < */ + return (strcmp(opnd1, opnd2) < 0); + case TO_STGT: /* > */ + return (strcmp(opnd1, opnd2) > 0); + case TO_INTEQ: /* -eq */ + case TO_INTNE: /* -ne */ + case TO_INTGE: /* -ge */ + case TO_INTGT: /* -gt */ + case TO_INTLE: /* -le */ + case TO_INTLT: /* -lt */ + if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) || + !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) { + /* error already printed.. */ + te->flags |= TEF_ERROR; + return (1); + } + switch ((int)op) { + case TO_INTEQ: + return (v1 == v2); + case TO_INTNE: + return (v1 != v2); + case TO_INTGE: + return (v1 >= v2); + case TO_INTGT: + return (v1 > v2); + case TO_INTLE: + return (v1 <= v2); + case TO_INTLT: + return (v1 < v2); + } + case TO_FILNT: /* -nt */ + /* ksh88/ksh93 succeed if file2 can't be stated + * (subtly different from 'does not exist'). + */ + return (stat(opnd1, &b1) == 0 && + (((s = stat(opnd2, &b2)) == 0 && + b1.st_mtime > b2.st_mtime) || s < 0)); + case TO_FILOT: /* -ot */ + /* ksh88/ksh93 succeed if file1 can't be stated + * (subtly different from 'does not exist'). + */ + return (stat(opnd2, &b2) == 0 && + (((s = stat(opnd1, &b1)) == 0 && + b1.st_mtime < b2.st_mtime) || s < 0)); + case TO_FILEQ: /* -ef */ + return (stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 && + b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); + } + (*te->error)(te, 0, "internal error: unknown op"); + return (1); +} + +/* On most/all unixen, access() says everything is executable for root... */ +static int +test_eaccess(const char *pathl, int mode) +{ + int rv; + + if ((rv = access(pathl, mode)) == 0 && ksheuid == 0 && (mode & X_OK)) { + struct stat statb; + + if (stat(pathl, &statb) < 0) + rv = -1; + else if (S_ISDIR(statb.st_mode)) + rv = 0; + else + rv = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ? + 0 : -1; + } + return (rv); +} + +int +test_parse(Test_env *te) +{ + int rv; + + rv = test_oexpr(te, 1); + + if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END)) + (*te->error)(te, 0, "unexpected operator/operand"); + + return ((te->flags & TEF_ERROR) ? T_ERR_EXIT : !rv); +} + +static int +test_oexpr(Test_env *te, bool do_eval) +{ + int rv; + + if ((rv = test_aexpr(te, do_eval))) + do_eval = false; + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR)) + return (test_oexpr(te, do_eval) || rv); + return (rv); +} + +static int +test_aexpr(Test_env *te, bool do_eval) +{ + int rv; + + if (!(rv = test_nexpr(te, do_eval))) + do_eval = false; + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND)) + return (test_aexpr(te, do_eval) && rv); + return (rv); +} + +static int +test_nexpr(Test_env *te, bool do_eval) +{ + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT)) + return (!test_nexpr(te, do_eval)); + return (test_primary(te, do_eval)); +} + +static int +test_primary(Test_env *te, bool do_eval) +{ + const char *opnd1, *opnd2; + int rv; + Test_op op; + + if (te->flags & TEF_ERROR) + return (0); + if ((*te->isa)(te, TM_OPAREN)) { + rv = test_oexpr(te, do_eval); + if (te->flags & TEF_ERROR) + return (0); + if (!(*te->isa)(te, TM_CPAREN)) { + (*te->error)(te, 0, "missing closing paren"); + return (0); + } + return (rv); + } + /* + * Binary should have precedence over unary in this case + * so that something like test \( -f = -f \) is accepted + */ + if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end && + !test_isop(TM_BINOP, te->pos.wp[1]))) { + if ((op = (*te->isa)(te, TM_UNOP))) { + /* unary expression */ + opnd1 = (*te->getopnd)(te, op, do_eval); + if (!opnd1) { + (*te->error)(te, -1, "missing argument"); + return (0); + } + + return ((*te->eval)(te, op, opnd1, NULL, do_eval)); + } + } + opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval); + if (!opnd1) { + (*te->error)(te, 0, "expression expected"); + return (0); + } + if ((op = (*te->isa)(te, TM_BINOP))) { + /* binary expression */ + opnd2 = (*te->getopnd)(te, op, do_eval); + if (!opnd2) { + (*te->error)(te, -1, "missing second argument"); + return (0); + } + + return ((*te->eval)(te, op, opnd1, opnd2, do_eval)); + } + return ((*te->eval)(te, TO_STNZE, opnd1, NULL, do_eval)); +} + +/* + * Plain test (test and [ .. ]) specific routines. + */ + +/* + * Test if the current token is a whatever. Accepts the current token if + * it is. Returns 0 if it is not, non-zero if it is (in the case of + * TM_UNOP and TM_BINOP, the returned value is a Test_op). + */ +static Test_op +ptest_isa(Test_env *te, Test_meta meta) +{ + /* Order important - indexed by Test_meta values */ + static const char *const tokens[] = { + "-o", "-a", "!", "(", ")" + }; + Test_op rv; + + if (te->pos.wp >= te->wp_end) + return (meta == TM_END ? TO_NONNULL : TO_NONOP); + + if (meta == TM_UNOP || meta == TM_BINOP) + rv = test_isop(meta, *te->pos.wp); + else if (meta == TM_END) + rv = TO_NONOP; + else + rv = !strcmp(*te->pos.wp, tokens[(int)meta]) ? + TO_NONNULL : TO_NONOP; + + /* Accept the token? */ + if (rv != TO_NONOP) + te->pos.wp++; + + return (rv); +} + +static const char * +ptest_getopnd(Test_env *te, Test_op op, bool do_eval MKSH_A_UNUSED) +{ + if (te->pos.wp >= te->wp_end) + return (op == TO_FILTT ? "1" : NULL); + return (*te->pos.wp++); +} + +static void +ptest_error(Test_env *te, int ofs, const char *msg) +{ + const char *op; + + te->flags |= TEF_ERROR; + if ((op = te->pos.wp + ofs >= te->wp_end ? NULL : te->pos.wp[ofs])) + bi_errorf("%s: %s", op, msg); + else + bi_errorf("%s", msg); +} + +#ifndef MKSH_NO_LIMITS +#define SOFT 0x1 +#define HARD 0x2 + +struct limits { + const char *name; + int resource; /* resource to get/set */ + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static void print_ulimit(const struct limits *, int); +static int set_ulimit(const struct limits *, const char *, int); + +/* Magic to divine the 'm' and 'v' limits */ + +#ifdef RLIMIT_AS +#if !defined(RLIMIT_VMEM) || (RLIMIT_VMEM == RLIMIT_AS) || \ + !defined(RLIMIT_RSS) || (RLIMIT_VMEM == RLIMIT_RSS) +#define ULIMIT_V_IS_AS +#elif defined(RLIMIT_VMEM) +#if !defined(RLIMIT_RSS) || (RLIMIT_RSS == RLIMIT_AS) +#define ULIMIT_V_IS_AS +#else +#define ULIMIT_V_IS_VMEM +#endif +#endif +#endif + +#ifdef RLIMIT_RSS +#ifdef ULIMIT_V_IS_VMEM +#define ULIMIT_M_IS_RSS +#elif defined(RLIMIT_VMEM) && (RLIMIT_VMEM == RLIMIT_RSS) +#define ULIMIT_M_IS_VMEM +#else +#define ULIMIT_M_IS_RSS +#endif +#if defined(ULIMIT_M_IS_RSS) && defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS) +#undef ULIMIT_M_IS_RSS +#endif +#endif + +#if !defined(RLIMIT_AS) && !defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_VMEM) +#define ULIMIT_V_IS_VMEM +#endif + +#if !defined(ULIMIT_V_IS_VMEM) && defined(RLIMIT_VMEM) && \ + (!defined(RLIMIT_RSS) || (defined(RLIMIT_AS) && (RLIMIT_RSS == RLIMIT_AS))) +#define ULIMIT_M_IS_VMEM +#endif + +#if defined(ULIMIT_M_IS_VMEM) && defined(RLIMIT_AS) && \ + (RLIMIT_VMEM == RLIMIT_AS) +#undef ULIMIT_M_IS_VMEM +#endif + + +int +c_ulimit(const char **wp) +{ + static const struct limits limits[] = { + /* do not use options -H, -S or -a or change the order */ +#ifdef RLIMIT_CPU + { "time(cpu-seconds)", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file(blocks)", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_CORE + { "coredump(blocks)", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_DATA + { "data(KiB)", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack(KiB)", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "lockedmem(KiB)", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_NPROC + { "processes", RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_SWAP + { "swap(KiB)", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_LOCKS + { "flocks", RLIMIT_LOCKS, -1, 'L' }, +#endif +#ifdef RLIMIT_TIME + { "humantime(seconds)", RLIMIT_TIME, 1, 'T' }, +#endif +#ifdef RLIMIT_NOVMON + { "vnodemonitors", RLIMIT_NOVMON, 1, 'V' }, +#endif +#ifdef RLIMIT_SIGPENDING + { "sigpending", RLIMIT_SIGPENDING, 1, 'i' }, +#endif +#ifdef RLIMIT_MSGQUEUE + { "msgqueue(bytes)", RLIMIT_MSGQUEUE, 1, 'q' }, +#endif +#ifdef RLIMIT_AIO_MEM + { "AIOlockedmem(KiB)", RLIMIT_AIO_MEM, 1024, 'M' }, +#endif +#ifdef RLIMIT_AIO_OPS + { "AIOoperations", RLIMIT_AIO_OPS, 1, 'O' }, +#endif +#ifdef RLIMIT_TCACHE + { "cachedthreads", RLIMIT_TCACHE, 1, 'C' }, +#endif +#ifdef RLIMIT_SBSIZE + { "sockbufsiz(KiB)", RLIMIT_SBSIZE, 1024, 'B' }, +#endif +#ifdef RLIMIT_PTHREAD + { "threadsperprocess", RLIMIT_PTHREAD, 1, 'P' }, +#endif +#ifdef RLIMIT_NICE + { "maxnice", RLIMIT_NICE, 1, 'e' }, +#endif +#ifdef RLIMIT_RTPRIO + { "maxrtprio", RLIMIT_RTPRIO, 1, 'r' }, +#endif +#if defined(ULIMIT_M_IS_RSS) + { "resident-set(KiB)", RLIMIT_RSS, 1024, 'm' }, +#elif defined(ULIMIT_M_IS_VMEM) + { "memory(KiB)", RLIMIT_VMEM, 1024, 'm' }, +#endif +#if defined(ULIMIT_V_IS_VMEM) + { "virtual-memory(KiB)", RLIMIT_VMEM, 1024, 'v' }, +#elif defined(ULIMIT_V_IS_AS) + { "address-space(KiB)", RLIMIT_AS, 1024, 'v' }, +#endif + { NULL, 0, 0, 0 } + }; + static char opts[3 + NELEM(limits)]; + int how = SOFT | HARD, optc, what = 'f'; + bool all = false; + const struct limits *l; + + if (!opts[0]) { + /* build options string on first call - yuck */ + char *p = opts; + + *p++ = 'H'; *p++ = 'S'; *p++ = 'a'; + for (l = limits; l->name; l++) + *p++ = l->option; + *p = '\0'; + } + + while ((optc = ksh_getopt(wp, &builtin_opt, opts)) != -1) + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = true; + break; + case '?': + bi_errorf("usage: ulimit [-acdfHLlmnpSsTtvw] [value]"); + return (1); + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) { + internal_warningf("ulimit: %c", what); + return (1); + } + + if (wp[builtin_opt.optind]) { + if (all || wp[builtin_opt.optind + 1]) { + bi_errorf("too many arguments"); + return (1); + } + return (set_ulimit(l, wp[builtin_opt.optind], how)); + } + if (!all) + print_ulimit(l, how); + else for (l = limits; l->name; l++) { + shprintf("%-20s ", l->name); + print_ulimit(l, how); + } + return (0); +} + +static int +set_ulimit(const struct limits *l, const char *v, int how) +{ + rlim_t val = (rlim_t)0; + struct rlimit limit; + + if (strcmp(v, "unlimited") == 0) + val = (rlim_t)RLIM_INFINITY; + else { + mksh_ari_t rval; + + if (!evaluate(v, &rval, KSH_RETURN_ERROR, false)) + return (1); + /* + * Avoid problems caused by typos that evaluate misses due + * to evaluating unset parameters to 0... + * If this causes problems, will have to add parameter to + * evaluate() to control if unset params are 0 or an error. + */ + if (!rval && !ksh_isdigit(v[0])) { + bi_errorf("invalid %s limit: %s", l->name, v); + return (1); + } + val = (rlim_t)((rlim_t)rval * l->factor); + } + + if (getrlimit(l->resource, &limit) < 0) { + /* some cannot be read, e.g. Linux RLIMIT_LOCKS */ + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + } + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (!setrlimit(l->resource, &limit)) + return (0); + if (errno == EPERM) + bi_errorf("%s exceeds allowable %s limit", v, l->name); + else + bi_errorf("bad %s limit: %s", l->name, strerror(errno)); + return (1); +} + +static void +print_ulimit(const struct limits *l, int how) +{ + rlim_t val = (rlim_t)0; + struct rlimit limit; + + if (getrlimit(l->resource, &limit)) { + shf_puts("unknown\n", shl_stdout); + return; + } + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + if (val == (rlim_t)RLIM_INFINITY) + shf_puts("unlimited\n", shl_stdout); + else + shprintf("%ld\n", (long)(val / l->factor)); +} +#endif + +int +c_rename(const char **wp) +{ + int rv = 1; + + if (wp == NULL /* argv */ || + wp[0] == NULL /* name of builtin */ || + wp[1] == NULL /* first argument */ || + wp[2] == NULL /* second argument */ || + wp[3] != NULL /* no further args please */) + bi_errorf(T_synerr); + else if ((rv = rename(wp[1], wp[2])) != 0) { + rv = errno; + bi_errorf("failed: %s", strerror(rv)); + } + + return (rv); +} + +int +c_realpath(const char **wp) +{ + int rv = 1; + char *buf; + + if (wp != NULL && wp[0] != NULL && wp[1] != NULL) { + if (strcmp(wp[1], "--")) { + if (wp[2] == NULL) { + wp += 1; + rv = 0; + } + } else { + if (wp[2] != NULL && wp[3] == NULL) { + wp += 2; + rv = 0; + } + } + } + + if (rv) + bi_errorf(T_synerr); + else if ((buf = do_realpath(*wp)) == NULL) { + rv = errno; + bi_errorf("%s: %s", *wp, strerror(rv)); + if ((unsigned int)rv > 255) + rv = 255; + } else { + shprintf("%s\n", buf); + afree(buf, ATEMP); + } + + return (rv); +} diff --git a/mksh/src/histrap.c b/mksh/src/histrap.c new file mode 100644 index 0000000..2ac4c38 --- /dev/null +++ b/mksh/src/histrap.c @@ -0,0 +1,1483 @@ +/* $OpenBSD: history.c,v 1.39 2010/05/19 17:36:08 jasper Exp $ */ +/* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" +#if HAVE_PERSISTENT_HISTORY +#include <sys/file.h> +#endif + +__RCSID("$MirOS: src/bin/mksh/histrap.c,v 1.98 2010/07/24 17:08:29 tg Exp $"); + +/*- + * MirOS: This is the default mapping type, and need not be specified. + * IRIX doesn't have this constant. + */ +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +Trap sigtraps[NSIG + 1]; +static struct sigaction Sigact_ign; + +#if HAVE_PERSISTENT_HISTORY +static int hist_count_lines(unsigned char *, int); +static int hist_shrink(unsigned char *, int); +static unsigned char *hist_skip_back(unsigned char *,int *,int); +static void histload(Source *, unsigned char *, int); +static void histinsert(Source *, int, const char *); +static void writehistfile(int, char *); +static int sprinkle(int); +#endif + +static int hist_execute(char *); +static int hist_replace(char **, const char *, const char *, bool); +static char **hist_get(const char *, bool, bool); +static char **hist_get_oldest(void); +static void histbackup(void); + +static char **current; /* current position in history[] */ +static int hstarted; /* set after hist_init() called */ +static Source *hist_source; + +#if HAVE_PERSISTENT_HISTORY +static char *hname; /* current name of history file */ +static int histfd; +static int hsize; +#endif + +int +c_fc(const char **wp) +{ + struct shf *shf; + struct temp *tf; + const char *p; + char *editor = NULL; + bool gflag = false, lflag = false, nflag = false, rflag = false, + sflag = false; + int optc; + const char *first = NULL, *last = NULL; + char **hfirst, **hlast, **hp; + + if (!Flag(FTALKING_I)) { + bi_errorf("history functions not available"); + return (1); + } + + while ((optc = ksh_getopt(wp, &builtin_opt, + "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) + switch (optc) { + case 'e': + p = builtin_opt.optarg; + if (ksh_isdash(p)) + sflag = true; + else { + size_t len = strlen(p); + editor = alloc(len + 4, ATEMP); + memcpy(editor, p, len); + memcpy(editor + len, " $_", 4); + } + break; + case 'g': /* non-AT&T ksh */ + gflag = true; + break; + case 'l': + lflag = true; + break; + case 'n': + nflag = true; + break; + case 'r': + rflag = true; + break; + case 's': /* POSIX version of -e - */ + sflag = true; + break; + /* kludge city - accept -num as -- -num (kind of) */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + p = shf_smprintf("-%c%s", + optc, builtin_opt.optarg); + if (!first) + first = p; + else if (!last) + last = p; + else { + bi_errorf("too many arguments"); + return (1); + } + break; + case '?': + return (1); + } + wp += builtin_opt.optind; + + /* Substitute and execute command */ + if (sflag) { + char *pat = NULL, *rep = NULL; + + if (editor || lflag || nflag || rflag) { + bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); + return (1); + } + + /* Check for pattern replacement argument */ + if (*wp && **wp && (p = cstrchr(*wp + 1, '='))) { + strdupx(pat, *wp, ATEMP); + rep = pat + (p - *wp); + *rep++ = '\0'; + wp++; + } + /* Check for search prefix */ + if (!first && (first = *wp)) + wp++; + if (last || *wp) { + bi_errorf("too many arguments"); + return (1); + } + + hp = first ? hist_get(first, false, false) : + hist_get_newest(false); + if (!hp) + return (1); + return (hist_replace(hp, pat, rep, gflag)); + } + + if (editor && (lflag || nflag)) { + bi_errorf("can't use -l, -n with -e"); + return (1); + } + + if (!first && (first = *wp)) + wp++; + if (!last && (last = *wp)) + wp++; + if (*wp) { + bi_errorf("too many arguments"); + return (1); + } + if (!first) { + hfirst = lflag ? hist_get("-16", true, true) : + hist_get_newest(false); + if (!hfirst) + return (1); + /* can't fail if hfirst didn't fail */ + hlast = hist_get_newest(false); + } else { + /* POSIX says not an error if first/last out of bounds + * when range is specified; AT&T ksh and pdksh allow out of + * bounds for -l as well. + */ + hfirst = hist_get(first, (lflag || last) ? true : false, lflag); + if (!hfirst) + return (1); + hlast = last ? hist_get(last, true, lflag) : + (lflag ? hist_get_newest(false) : hfirst); + if (!hlast) + return (1); + } + if (hfirst > hlast) { + char **temp; + + temp = hfirst; hfirst = hlast; hlast = temp; + rflag = !rflag; /* POSIX */ + } + + /* List history */ + if (lflag) { + char *s, *t; + + for (hp = rflag ? hlast : hfirst; + hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) { + if (!nflag) + shf_fprintf(shl_stdout, "%d", + hist_source->line - (int)(histptr - hp)); + shf_putc('\t', shl_stdout); + /* print multi-line commands correctly */ + s = *hp; + while ((t = strchr(s, '\n'))) { + *t = '\0'; + shf_fprintf(shl_stdout, "%s\n\t", s); + *t++ = '\n'; + s = t; + } + shf_fprintf(shl_stdout, "%s\n", s); + } + shf_flush(shl_stdout); + return (0); + } + + /* Run editor on selected lines, then run resulting commands */ + + tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps); + if (!(shf = tf->shf)) { + bi_errorf("cannot create temp file %s - %s", + tf->name, strerror(errno)); + return (1); + } + for (hp = rflag ? hlast : hfirst; + hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) + shf_fprintf(shf, "%s\n", *hp); + if (shf_close(shf) == EOF) { + bi_errorf("error writing temporary file - %s", strerror(errno)); + return (1); + } + + /* Ignore setstr errors here (arbitrary) */ + setstr(local("_", false), tf->name, KSH_RETURN_ERROR); + + /* XXX: source should not get trashed by this.. */ + { + Source *sold = source; + int ret; + + ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); + source = sold; + if (ret) + return (ret); + } + + { + struct stat statb; + XString xs; + char *xp; + int n; + + if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { + bi_errorf("cannot open temp file %s", tf->name); + return (1); + } + + n = stat(tf->name, &statb) < 0 ? 128 : statb.st_size + 1; + Xinit(xs, xp, n, hist_source->areap); + while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { + xp += n; + if (Xnleft(xs, xp) <= 0) + XcheckN(xs, xp, Xlength(xs, xp)); + } + if (n < 0) { + bi_errorf("error reading temp file %s - %s", + tf->name, strerror(shf_errno(shf))); + shf_close(shf); + return (1); + } + shf_close(shf); + *xp = '\0'; + strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); + return (hist_execute(Xstring(xs, xp))); + } +} + +/* Save cmd in history, execute cmd (cmd gets trashed) */ +static int +hist_execute(char *cmd) +{ + Source *sold; + int ret; + char *p, *q; + + histbackup(); + + for (p = cmd; p; p = q) { + if ((q = strchr(p, '\n'))) { + *q++ = '\0'; /* kill the newline */ + if (!*q) /* ignore trailing newline */ + q = NULL; + } + histsave(&hist_source->line, p, true, true); + + shellf("%s\n", p); /* POSIX doesn't say this is done... */ + if (q) /* restore \n (trailing \n not restored) */ + q[-1] = '\n'; + } + + /* + * Commands are executed here instead of pushing them onto the + * input 'cause POSIX says the redirection and variable assignments + * in + * X=y fc -e - 42 2> /dev/null + * are to effect the repeated commands environment. + */ + /* XXX: source should not get trashed by this.. */ + sold = source; + ret = command(cmd, 0); + source = sold; + return (ret); +} + +static int +hist_replace(char **hp, const char *pat, const char *rep, bool globr) +{ + char *line; + + if (!pat) + strdupx(line, *hp, ATEMP); + else { + char *s, *s1; + int pat_len = strlen(pat); + int rep_len = strlen(rep); + int len; + XString xs; + char *xp; + bool any_subst = false; + + Xinit(xs, xp, 128, ATEMP); + for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || globr); + s = s1 + pat_len) { + any_subst = true; + len = s1 - s; + XcheckN(xs, xp, len + rep_len); + memcpy(xp, s, len); /* first part */ + xp += len; + memcpy(xp, rep, rep_len); /* replacement */ + xp += rep_len; + } + if (!any_subst) { + bi_errorf("substitution failed"); + return (1); + } + len = strlen(s) + 1; + XcheckN(xs, xp, len); + memcpy(xp, s, len); + xp += len; + line = Xclose(xs, xp); + } + return (hist_execute(line)); +} + +/* + * get pointer to history given pattern + * pattern is a number or string + */ +static char ** +hist_get(const char *str, bool approx, bool allow_cur) +{ + char **hp = NULL; + int n; + + if (getn(str, &n)) { + hp = histptr + (n < 0 ? n : (n - hist_source->line)); + if ((ptrdiff_t)hp < (ptrdiff_t)history) { + if (approx) + hp = hist_get_oldest(); + else { + bi_errorf("%s: not in history", str); + hp = NULL; + } + } else if ((ptrdiff_t)hp > (ptrdiff_t)histptr) { + if (approx) + hp = hist_get_newest(allow_cur); + else { + bi_errorf("%s: not in history", str); + hp = NULL; + } + } else if (!allow_cur && hp == histptr) { + bi_errorf("%s: invalid range", str); + hp = NULL; + } + } else { + int anchored = *str == '?' ? (++str, 0) : 1; + + /* the -1 is to avoid the current fc command */ + if ((n = findhist(histptr - history - 1, 0, str, anchored)) < 0) + bi_errorf("%s: not in history", str); + else + hp = &history[n]; + } + return (hp); +} + +/* Return a pointer to the newest command in the history */ +char ** +hist_get_newest(bool allow_cur) +{ + if (histptr < history || (!allow_cur && histptr == history)) { + bi_errorf("no history (yet)"); + return (NULL); + } + return (allow_cur ? histptr : histptr - 1); +} + +/* Return a pointer to the oldest command in the history */ +static char ** +hist_get_oldest(void) +{ + if (histptr <= history) { + bi_errorf("no history (yet)"); + return (NULL); + } + return (history); +} + +/******************************/ +/* Back up over last histsave */ +/******************************/ +static void +histbackup(void) +{ + static int last_line = -1; + + if (histptr >= history && last_line != hist_source->line) { + hist_source->line--; + afree(*histptr, APERM); + histptr--; + last_line = hist_source->line; + } +} + +/* + * Return the current position. + */ +char ** +histpos(void) +{ + return (current); +} + +int +histnum(int n) +{ + int last = histptr - history; + + if (n < 0 || n >= last) { + current = histptr; + return (last); + } else { + current = &history[n]; + return (n); + } +} + +/* + * This will become unnecessary if hist_get is modified to allow + * searching from positions other than the end, and in either + * direction. + */ +int +findhist(int start, int fwd, const char *str, int anchored) +{ + char **hp; + int maxhist = histptr - history; + int incr = fwd ? 1 : -1; + int len = strlen(str); + + if (start < 0 || start >= maxhist) + start = maxhist; + + hp = &history[start]; + for (; hp >= history && hp <= histptr; hp += incr) + if ((anchored && strncmp(*hp, str, len) == 0) || + (!anchored && strstr(*hp, str))) + return (hp - history); + + return (-1); +} + +int +findhistrel(const char *str) +{ + int maxhist = histptr - history; + int start = maxhist - 1; + int rec; + + getn(str, &rec); + if (rec == 0) + return (-1); + if (rec > 0) { + if (rec > maxhist) + return (-1); + return (rec - 1); + } + if (rec > maxhist) + return (-1); + return (start + rec + 1); +} + +/* + * set history + * this means reallocating the dataspace + */ +void +sethistsize(int n) +{ + if (n > 0 && n != histsize) { + int cursize = histptr - history; + + /* save most recent history */ + if (n < cursize) { + memmove(history, histptr - n, n * sizeof(char *)); + cursize = n; + } + + history = aresize(history, n * sizeof(char *), APERM); + + histsize = n; + histptr = history + cursize; + } +} + +#if HAVE_PERSISTENT_HISTORY +/* + * set history file + * This can mean reloading/resetting/starting history file + * maintenance + */ +void +sethistfile(const char *name) +{ + /* if not started then nothing to do */ + if (hstarted == 0) + return; + + /* if the name is the same as the name we have */ + if (hname && strcmp(hname, name) == 0) + return; + + /* + * its a new name - possibly + */ + if (histfd) { + /* yes the file is open */ + (void)close(histfd); + histfd = 0; + hsize = 0; + afree(hname, APERM); + hname = NULL; + /* let's reset the history */ + histptr = history - 1; + hist_source->line = 0; + } + + hist_init(hist_source); +} +#endif + +/* + * initialise the history vector + */ +void +init_histvec(void) +{ + if (history == (char **)NULL) { + histsize = HISTORYSIZE; + history = alloc(histsize * sizeof(char *), APERM); + histptr = history - 1; + } +} + + +/* + * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to + * a) permit HISTSIZE to control number of lines of history stored + * b) maintain a physical history file + * + * It turns out that there is a lot of ghastly hackery here + */ + +#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY +/* do not save command in history but possibly sync */ +bool +histsync(void) +{ + bool changed = false; + + if (histfd) { + int lno = hist_source->line; + + hist_source->line++; + writehistfile(0, NULL); + hist_source->line--; + + if (lno != hist_source->line) + changed = true; + } + + return (changed); +} +#endif + +/* + * save command in history + */ +void +histsave(int *lnp, const char *cmd, bool dowrite MKSH_A_UNUSED, bool ignoredups) +{ + char **hp; + char *c, *cp; + + strdupx(c, cmd, APERM); + if ((cp = strchr(c, '\n')) != NULL) + *cp = '\0'; + + if (ignoredups && !strcmp(c, *histptr) +#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY + && !histsync() +#endif + ) { + afree(c, APERM); + return; + } + ++*lnp; + +#if HAVE_PERSISTENT_HISTORY + if (histfd && dowrite) + writehistfile(*lnp, c); +#endif + + hp = histptr; + + if (++hp >= history + histsize) { /* remove oldest command */ + afree(*history, APERM); + for (hp = history; hp < history + histsize - 1; hp++) + hp[0] = hp[1]; + } + *hp = c; + histptr = hp; +} + +/* + * Write history data to a file nominated by HISTFILE + * if HISTFILE is unset then history still happens, but + * the data is not written to a file + * All copies of ksh looking at the file will maintain the + * same history. This is ksh behaviour. + * + * This stuff uses mmap() + * if your system ain't got it - then you'll have to undef HISTORYFILE + */ + +/* + * Open a history file + * Format is: + * Bytes 1, 2: + * HMAGIC - just to check that we are dealing with + * the correct object + * Then follows a number of stored commands + * Each command is + * <command byte><command number(4 bytes)><bytes><null> + */ +#define HMAGIC1 0xab +#define HMAGIC2 0xcd +#define COMMAND 0xff + +void +hist_init(Source *s) +{ +#if HAVE_PERSISTENT_HISTORY + unsigned char *base; + int lines, fd, rv = 0; +#endif + + if (Flag(FTALKING) == 0) + return; + + hstarted = 1; + + hist_source = s; + +#if HAVE_PERSISTENT_HISTORY + if ((hname = str_val(global("HISTFILE"))) == NULL) + return; + strdupx(hname, hname, APERM); + + retry: + /* we have a file and are interactive */ + if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) + return; + + histfd = savefd(fd); + if (histfd != fd) + close(fd); + + (void)flock(histfd, LOCK_EX); + + hsize = lseek(histfd, (off_t)0, SEEK_END); + + if (hsize == 0) { + /* add magic */ + if (sprinkle(histfd)) { + hist_finish(); + return; + } + } else if (hsize > 0) { + /* + * we have some data + */ + base = (void *)mmap(NULL, hsize, PROT_READ, + MAP_FILE | MAP_PRIVATE, histfd, (off_t)0); + /* + * check on its validity + */ + if (base == (unsigned char *)MAP_FAILED || + *base != HMAGIC1 || base[1] != HMAGIC2) { + if (base != (unsigned char *)MAP_FAILED) + munmap((caddr_t)base, hsize); + hist_finish(); + if (unlink(hname) /* fails */) + goto hiniterr; + goto retry; + } + if (hsize > 2) { + lines = hist_count_lines(base+2, hsize-2); + if (lines > histsize) { + /* we need to make the file smaller */ + if (hist_shrink(base, hsize)) + rv = unlink(hname); + munmap((caddr_t)base, hsize); + hist_finish(); + if (rv) { + hiniterr: + bi_errorf("cannot unlink HISTFILE %s" + " - %s", hname, strerror(errno)); + hsize = 0; + return; + } + goto retry; + } + } + histload(hist_source, base+2, hsize-2); + munmap((caddr_t)base, hsize); + } + (void)flock(histfd, LOCK_UN); + hsize = lseek(histfd, (off_t)0, SEEK_END); +#endif +} + +#if HAVE_PERSISTENT_HISTORY +typedef enum state { + shdr, /* expecting a header */ + sline, /* looking for a null byte to end the line */ + sn1, /* bytes 1 to 4 of a line no */ + sn2, sn3, sn4 +} State; + +static int +hist_count_lines(unsigned char *base, int bytes) +{ + State state = shdr; + int lines = 0; + + while (bytes--) { + switch (state) { + case shdr: + if (*base == COMMAND) + state = sn1; + break; + case sn1: + state = sn2; break; + case sn2: + state = sn3; break; + case sn3: + state = sn4; break; + case sn4: + state = sline; break; + case sline: + if (*base == '\0') { + lines++; + state = shdr; + } + } + base++; + } + return (lines); +} + +/* + * Shrink the history file to histsize lines + */ +static int +hist_shrink(unsigned char *oldbase, int oldbytes) +{ + int fd, rv = 0; + char *nfile = NULL; + struct stat statb; + unsigned char *nbase = oldbase; + int nbytes = oldbytes; + + nbase = hist_skip_back(nbase, &nbytes, histsize); + if (nbase == NULL) + return (1); + if (nbase == oldbase) + return (0); + + /* + * create temp file + */ + nfile = shf_smprintf("%s.%d", hname, (int)procpid); + if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) + goto errout; + if (fstat(histfd, &statb) >= 0 && + chown(nfile, statb.st_uid, statb.st_gid)) + goto errout; + + if (sprinkle(fd) || write(fd, nbase, nbytes) != nbytes) + goto errout; + close(fd); + fd = -1; + + /* + * rename + */ + if (rename(nfile, hname) < 0) { + errout: + if (fd >= 0) { + close(fd); + if (nfile) + unlink(nfile); + } + rv = 1; + } + afree(nfile, ATEMP); + return (rv); +} + +/* + * find a pointer to the data 'no' back from the end of the file + * return the pointer and the number of bytes left + */ +static unsigned char * +hist_skip_back(unsigned char *base, int *bytes, int no) +{ + int lines = 0; + unsigned char *ep; + + for (ep = base + *bytes; --ep > base; ) { + /* + * this doesn't really work: the 4 byte line number that + * is encoded after the COMMAND byte can itself contain + * the COMMAND byte.... + */ + for (; ep > base && *ep != COMMAND; ep--) + ; + if (ep == base) + break; + if (++lines == no) { + *bytes = *bytes - ((char *)ep - (char *)base); + return (ep); + } + } + return (NULL); +} + +/* + * load the history structure from the stored data + */ +static void +histload(Source *s, unsigned char *base, int bytes) +{ + State state; + int lno = 0; + unsigned char *line = NULL; + + for (state = shdr; bytes-- > 0; base++) { + switch (state) { + case shdr: + if (*base == COMMAND) + state = sn1; + break; + case sn1: + lno = (((*base)&0xff)<<24); + state = sn2; + break; + case sn2: + lno |= (((*base)&0xff)<<16); + state = sn3; + break; + case sn3: + lno |= (((*base)&0xff)<<8); + state = sn4; + break; + case sn4: + lno |= (*base)&0xff; + line = base+1; + state = sline; + break; + case sline: + if (*base == '\0') { + /* worry about line numbers */ + if (histptr >= history && lno-1 != s->line) { + /* a replacement ? */ + histinsert(s, lno, (char *)line); + } else { + s->line = lno--; + histsave(&lno, (char *)line, false, + false); + } + state = shdr; + } + } + } +} + +/* + * Insert a line into the history at a specified number + */ +static void +histinsert(Source *s, int lno, const char *line) +{ + char **hp; + + if (lno >= s->line - (histptr - history) && lno <= s->line) { + hp = &histptr[lno - s->line]; + if (*hp) + afree(*hp, APERM); + strdupx(*hp, line, APERM); + } +} + +/* + * write a command to the end of the history file + * This *MAY* seem easy but it's also necessary to check + * that the history file has not changed in size. + * If it has - then some other shell has written to it + * and we should read those commands to update our history + */ +static void +writehistfile(int lno, char *cmd) +{ + int sizenow; + unsigned char *base; + unsigned char *news; + int bytes; + unsigned char hdr[5]; + + (void)flock(histfd, LOCK_EX); + sizenow = lseek(histfd, (off_t)0, SEEK_END); + if (sizenow != hsize) { + /* + * Things have changed + */ + if (sizenow > hsize) { + /* someone has added some lines */ + bytes = sizenow - hsize; + base = (void *)mmap(NULL, sizenow, PROT_READ, + MAP_FILE | MAP_PRIVATE, histfd, (off_t)0); + if (base == (unsigned char *)MAP_FAILED) + goto bad; + news = base + hsize; + if (*news != COMMAND) { + munmap((caddr_t)base, sizenow); + goto bad; + } + hist_source->line--; + histload(hist_source, news, bytes); + hist_source->line++; + lno = hist_source->line; + munmap((caddr_t)base, sizenow); + hsize = sizenow; + } else { + /* it has shrunk */ + /* but to what? */ + /* we'll give up for now */ + goto bad; + } + } + if (cmd) { + /* + * we can write our bit now + */ + hdr[0] = COMMAND; + hdr[1] = (lno>>24)&0xff; + hdr[2] = (lno>>16)&0xff; + hdr[3] = (lno>>8)&0xff; + hdr[4] = lno&0xff; + bytes = strlen(cmd) + 1; + if ((write(histfd, hdr, 5) != 5) || + (write(histfd, cmd, bytes) != bytes)) + goto bad; + hsize = lseek(histfd, (off_t)0, SEEK_END); + } + (void)flock(histfd, LOCK_UN); + return; + bad: + hist_finish(); +} + +void +hist_finish(void) +{ + (void)flock(histfd, LOCK_UN); + (void)close(histfd); + histfd = 0; +} + +/* + * add magic to the history file + */ +static int +sprinkle(int fd) +{ + static const unsigned char mag[] = { HMAGIC1, HMAGIC2 }; + + return (write(fd, mag, 2) != 2); +} +#endif + +#if !HAVE_SYS_SIGNAME +static const struct mksh_sigpair { + const char *const name; + int nr; +} mksh_sigpairs[] = { +#include "signames.inc" + { NULL, 0 } +}; +#endif + +void +inittraps(void) +{ + int i; + const char *cs; + + /* Populate sigtraps based on sys_signame and sys_siglist. */ + for (i = 0; i <= NSIG; i++) { + sigtraps[i].signal = i; + if (i == SIGERR_) { + sigtraps[i].name = "ERR"; + sigtraps[i].mess = "Error handler"; + } else { +#if HAVE_SYS_SIGNAME + cs = sys_signame[i]; +#else + const struct mksh_sigpair *pair = mksh_sigpairs; + while ((pair->nr != i) && (pair->name != NULL)) + ++pair; + cs = pair->name; +#endif + if ((cs == NULL) || + (cs[0] == '\0')) + sigtraps[i].name = shf_smprintf("%d", i); + else { + char *s; + + if (!strncasecmp(cs, "SIG", 3)) + cs += 3; + strdupx(s, cs, APERM); + sigtraps[i].name = s; + while ((*s = ksh_toupper(*s))) + ++s; + } +#if HAVE_SYS_SIGLIST + sigtraps[i].mess = sys_siglist[i]; +#elif HAVE_STRSIGNAL + sigtraps[i].mess = strsignal(i); +#else + sigtraps[i].mess = NULL; +#endif + if ((sigtraps[i].mess == NULL) || + (sigtraps[i].mess[0] == '\0')) + sigtraps[i].mess = shf_smprintf("Signal %d", i); + } + } + sigtraps[SIGEXIT_].name = "EXIT"; /* our name for signal 0 */ + + (void)sigemptyset(&Sigact_ign.sa_mask); + Sigact_ign.sa_flags = 0; /* interruptible */ + Sigact_ign.sa_handler = SIG_IGN; + + sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR; + sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR; + sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */ + sigtraps[SIGHUP].flags |= TF_FATAL; + sigtraps[SIGCHLD].flags |= TF_SHELL_USES; + + /* these are always caught so we can clean up any temporary files. */ + setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG); + setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG); + setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG); + setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG); +} + +static void alarm_catcher(int sig); + +void +alarm_init(void) +{ + sigtraps[SIGALRM].flags |= TF_SHELL_USES; + setsig(&sigtraps[SIGALRM], alarm_catcher, + SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); +} + +/* ARGSUSED */ +static void +alarm_catcher(int sig MKSH_A_UNUSED) +{ + /* this runs inside interrupt context, with errno saved */ + + if (ksh_tmout_state == TMOUT_READING) { + int left = alarm(0); + + if (left == 0) { + ksh_tmout_state = TMOUT_LEAVING; + intrsig = 1; + } else + alarm(left); + } +} + +Trap * +gettrap(const char *name, int igncase) +{ + int n = NSIG + 1; + Trap *p; + const char *n2; + int (*cmpfunc)(const char *, const char *) = strcmp; + + if (ksh_isdigit(*name)) { + if (getn(name, &n) && 0 <= n && n < NSIG) + return (&sigtraps[n]); + else + return (NULL); + } + + n2 = strncasecmp(name, "SIG", 3) ? NULL : name + 3; + if (igncase) + cmpfunc = strcasecmp; + for (p = sigtraps; --n >= 0; p++) + if (!cmpfunc(p->name, name) || (n2 && !cmpfunc(p->name, n2))) + return (p); + return (NULL); +} + +/* + * trap signal handler + */ +void +trapsig(int i) +{ + Trap *p = &sigtraps[i]; + int errno_ = errno; + + trap = p->set = 1; + if (p->flags & TF_DFL_INTR) + intrsig = 1; + if ((p->flags & TF_FATAL) && !p->trap) { + fatal_trap = 1; + intrsig = 1; + } + if (p->shtrap) + (*p->shtrap)(i); + errno = errno_; +} + +/* + * called when we want to allow the user to ^C out of something - won't + * work if user has trapped SIGINT. + */ +void +intrcheck(void) +{ + if (intrsig) + runtraps(TF_DFL_INTR|TF_FATAL); +} + +/* + * called after EINTR to check if a signal with normally causes process + * termination has been received. + */ +int +fatal_trap_check(void) +{ + int i; + Trap *p; + + /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */ + for (p = sigtraps, i = NSIG+1; --i >= 0; p++) + if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL))) + /* return value is used as an exit code */ + return (128 + p->signal); + return (0); +} + +/* + * Returns the signal number of any pending traps: ie, a signal which has + * occurred for which a trap has been set or for which the TF_DFL_INTR flag + * is set. + */ +int +trap_pending(void) +{ + int i; + Trap *p; + + for (p = sigtraps, i = NSIG+1; --i >= 0; p++) + if (p->set && ((p->trap && p->trap[0]) || + ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap))) + return (p->signal); + return (0); +} + +/* + * run any pending traps. If intr is set, only run traps that + * can interrupt commands. + */ +void +runtraps(int flag) +{ + int i; + Trap *p; + + if (ksh_tmout_state == TMOUT_LEAVING) { + ksh_tmout_state = TMOUT_EXECUTING; + warningf(false, "timed out waiting for input"); + unwind(LEXIT); + } else + /* + * XXX: this means the alarm will have no effect if a trap + * is caught after the alarm() was started...not good. + */ + ksh_tmout_state = TMOUT_EXECUTING; + if (!flag) + trap = 0; + if (flag & TF_DFL_INTR) + intrsig = 0; + if (flag & TF_FATAL) + fatal_trap = 0; + for (p = sigtraps, i = NSIG+1; --i >= 0; p++) + if (p->set && (!flag || + ((p->flags & flag) && p->trap == NULL))) + runtrap(p); +} + +void +runtrap(Trap *p) +{ + int i = p->signal; + char *trapstr = p->trap; + int oexstat; + int old_changed = 0; + + p->set = 0; + if (trapstr == NULL) { /* SIG_DFL */ + if (p->flags & TF_FATAL) { + /* eg, SIGHUP */ + exstat = 128 + i; + unwind(LLEAVE); + } + if (p->flags & TF_DFL_INTR) { + /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */ + exstat = 128 + i; + unwind(LINTR); + } + return; + } + if (trapstr[0] == '\0') /* SIG_IGN */ + return; + if (i == SIGEXIT_ || i == SIGERR_) { /* avoid recursion on these */ + old_changed = p->flags & TF_CHANGED; + p->flags &= ~TF_CHANGED; + p->trap = NULL; + } + oexstat = exstat; + /* + * Note: trapstr is fully parsed before anything is executed, thus + * no problem with afree(p->trap) in settrap() while still in use. + */ + command(trapstr, current_lineno); + exstat = oexstat; + if (i == SIGEXIT_ || i == SIGERR_) { + if (p->flags & TF_CHANGED) + /* don't clear TF_CHANGED */ + afree(trapstr, APERM); + else + p->trap = trapstr; + p->flags |= old_changed; + } +} + +/* clear pending traps and reset user's trap handlers; used after fork(2) */ +void +cleartraps(void) +{ + int i; + Trap *p; + + trap = 0; + intrsig = 0; + fatal_trap = 0; + for (i = NSIG+1, p = sigtraps; --i >= 0; p++) { + p->set = 0; + if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0])) + settrap(p, NULL); + } +} + +/* restore signals just before an exec(2) */ +void +restoresigs(void) +{ + int i; + Trap *p; + + for (i = NSIG+1, p = sigtraps; --i >= 0; p++) + if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL)) + setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL, + SS_RESTORE_CURR|SS_FORCE); +} + +void +settrap(Trap *p, const char *s) +{ + sig_t f; + + if (p->trap) + afree(p->trap, APERM); + strdupx(p->trap, s, APERM); /* handles s == 0 */ + p->flags |= TF_CHANGED; + f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN; + + p->flags |= TF_USER_SET; + if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL) + f = trapsig; + else if (p->flags & TF_SHELL_USES) { + if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) { + /* do what user wants at exec time */ + p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL); + if (f == SIG_IGN) + p->flags |= TF_EXEC_IGN; + else + p->flags |= TF_EXEC_DFL; + } + + /* + * assumes handler already set to what shell wants it + * (normally trapsig, but could be j_sigchld() or SIG_IGN) + */ + return; + } + + /* todo: should we let user know signal is ignored? how? */ + setsig(p, f, SS_RESTORE_CURR|SS_USER); +} + +/* + * Called by c_print() when writing to a co-process to ensure SIGPIPE won't + * kill shell (unless user catches it and exits) + */ +int +block_pipe(void) +{ + int restore_dfl = 0; + Trap *p = &sigtraps[SIGPIPE]; + + if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) { + setsig(p, SIG_IGN, SS_RESTORE_CURR); + if (p->flags & TF_ORIG_DFL) + restore_dfl = 1; + } else if (p->cursig == SIG_DFL) { + setsig(p, SIG_IGN, SS_RESTORE_CURR); + restore_dfl = 1; /* restore to SIG_DFL */ + } + return (restore_dfl); +} + +/* Called by c_print() to undo whatever block_pipe() did */ +void +restore_pipe(int restore_dfl) +{ + if (restore_dfl) + setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR); +} + +/* + * Set action for a signal. Action may not be set if original + * action was SIG_IGN, depending on the value of flags and FTALKING. + */ +int +setsig(Trap *p, sig_t f, int flags) +{ + struct sigaction sigact; + + if (p->signal == SIGEXIT_ || p->signal == SIGERR_) + return (1); + + /* + * First time setting this signal? If so, get and note the current + * setting. + */ + if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) { + sigaction(p->signal, &Sigact_ign, &sigact); + p->flags |= sigact.sa_handler == SIG_IGN ? + TF_ORIG_IGN : TF_ORIG_DFL; + p->cursig = SIG_IGN; + } + + /*- + * Generally, an ignored signal stays ignored, except if + * - the user of an interactive shell wants to change it + * - the shell wants for force a change + */ + if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) && + (!(flags & SS_USER) || !Flag(FTALKING))) + return (0); + + setexecsig(p, flags & SS_RESTORE_MASK); + + /* + * This is here 'cause there should be a way of clearing + * shtraps, but don't know if this is a sane way of doing + * it. At the moment, all users of shtrap are lifetime + * users (SIGALRM, SIGCHLD, SIGWINCH). + */ + if (!(flags & SS_USER)) + p->shtrap = (sig_t)NULL; + if (flags & SS_SHTRAP) { + p->shtrap = f; + f = trapsig; + } + + if (p->cursig != f) { + p->cursig = f; + (void)sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0 /* interruptible */; + sigact.sa_handler = f; + sigaction(p->signal, &sigact, NULL); + } + + return (1); +} + +/* control what signal is set to before an exec() */ +void +setexecsig(Trap *p, int restore) +{ + /* XXX debugging */ + if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) + internal_errorf("setexecsig: unset signal %d(%s)", + p->signal, p->name); + + /* restore original value for exec'd kids */ + p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL); + switch (restore & SS_RESTORE_MASK) { + case SS_RESTORE_CURR: /* leave things as they currently are */ + break; + case SS_RESTORE_ORIG: + p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL; + break; + case SS_RESTORE_DFL: + p->flags |= TF_EXEC_DFL; + break; + case SS_RESTORE_IGN: + p->flags |= TF_EXEC_IGN; + break; + } +} diff --git a/mksh/src/jobs.c b/mksh/src/jobs.c new file mode 100644 index 0000000..47326a1 --- /dev/null +++ b/mksh/src/jobs.c @@ -0,0 +1,1648 @@ +/* $OpenBSD: jobs.c,v 1.38 2009/12/12 04:28:44 deraadt Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/jobs.c,v 1.69 2010/07/04 17:33:54 tg Exp $"); + +#if HAVE_KILLPG +#define mksh_killpg killpg +#else +/* cross fingers and hope kill is killpg-endowed */ +#define mksh_killpg(p,s) kill(-(p), (s)) +#endif + +/* Order important! */ +#define PRUNNING 0 +#define PEXITED 1 +#define PSIGNALLED 2 +#define PSTOPPED 3 + +typedef struct proc Proc; +struct proc { + Proc *next; /* next process in pipeline (if any) */ + pid_t pid; /* process id */ + int state; + int status; /* wait status */ + char command[48]; /* process command string */ +}; + +/* Notify/print flag - j_print() argument */ +#define JP_NONE 0 /* don't print anything */ +#define JP_SHORT 1 /* print signals processes were killed by */ +#define JP_MEDIUM 2 /* print [job-num] -/+ command */ +#define JP_LONG 3 /* print [job-num] -/+ pid command */ +#define JP_PGRP 4 /* print pgrp */ + +/* put_job() flags */ +#define PJ_ON_FRONT 0 /* at very front */ +#define PJ_PAST_STOPPED 1 /* just past any stopped jobs */ + +/* Job.flags values */ +#define JF_STARTED 0x001 /* set when all processes in job are started */ +#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */ +#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */ +#define JF_XXCOM 0x008 /* set for $(command) jobs */ +#define JF_FG 0x010 /* running in foreground (also has tty pgrp) */ +#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */ +#define JF_CHANGED 0x040 /* process has changed state */ +#define JF_KNOWN 0x080 /* $! referenced */ +#define JF_ZOMBIE 0x100 /* known, unwaited process */ +#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_noityf()) */ +#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */ +#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */ + +typedef struct job Job; +struct job { + Job *next; /* next job in list */ + Proc *proc_list; /* process list */ + Proc *last_proc; /* last process in list */ + struct timeval systime; /* system time used by job */ + struct timeval usrtime; /* user time used by job */ + pid_t pgrp; /* process group of job */ + pid_t ppid; /* pid of process that forked job */ + int job; /* job number: %n */ + int flags; /* see JF_* */ + volatile int state; /* job state */ + int status; /* exit status of last process */ + int32_t age; /* number of jobs started */ + Coproc_id coproc_id; /* 0 or id of coprocess output pipe */ +#ifndef MKSH_UNEMPLOYED + struct termios ttystate;/* saved tty state for stopped jobs */ + pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ +#endif +}; + +/* Flags for j_waitj() */ +#define JW_NONE 0x00 +#define JW_INTERRUPT 0x01 /* ^C will stop the wait */ +#define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */ +#define JW_STOPPEDWAIT 0x04 /* wait even if job stopped */ + +/* Error codes for j_lookup() */ +#define JL_OK 0 +#define JL_NOSUCH 1 /* no such job */ +#define JL_AMBIG 2 /* %foo or %?foo is ambiguous */ +#define JL_INVALID 3 /* non-pid, non-% job id */ + +static const char *const lookup_msgs[] = { + null, + "no such job", + "ambiguous", + "argument must be %job or process id", + NULL +}; + +static Job *job_list; /* job list */ +static Job *last_job; +static Job *async_job; +static pid_t async_pid; + +static int nzombie; /* # of zombies owned by this process */ +static int32_t njobs; /* # of jobs started */ + +#ifndef CHILD_MAX +#define CHILD_MAX 25 +#endif + +/* held_sigchld is set if sigchld occurs before a job is completely started */ +static volatile sig_atomic_t held_sigchld; + +#ifndef MKSH_UNEMPLOYED +static struct shf *shl_j; +static bool ttypgrp_ok; /* set if can use tty pgrps */ +static pid_t restore_ttypgrp = -1; +static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU }; +#endif + +static void j_set_async(Job *); +static void j_startjob(Job *); +static int j_waitj(Job *, int, const char *); +static void j_sigchld(int); +static void j_print(Job *, int, struct shf *); +static Job *j_lookup(const char *, int *); +static Job *new_job(void); +static Proc *new_proc(void); +static void check_job(Job *); +static void put_job(Job *, int); +static void remove_job(Job *, const char *); +static int kill_job(Job *, int); + +/* initialise job control */ +void +j_init(void) +{ +#ifndef MKSH_UNEMPLOYED + bool mflagset = Flag(FMONITOR) != 127; + + Flag(FMONITOR) = 0; +#endif + + (void)sigemptyset(&sm_default); + sigprocmask(SIG_SETMASK, &sm_default, NULL); + + (void)sigemptyset(&sm_sigchld); + (void)sigaddset(&sm_sigchld, SIGCHLD); + + setsig(&sigtraps[SIGCHLD], j_sigchld, + SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); + +#ifndef MKSH_UNEMPLOYED + if (!mflagset && Flag(FTALKING)) + Flag(FMONITOR) = 1; + + /* + * shl_j is used to do asynchronous notification (used in + * an interrupt handler, so need a distinct shf) + */ + shl_j = shf_fdopen(2, SHF_WR, NULL); + + if (Flag(FMONITOR) || Flag(FTALKING)) { + int i; + + /* + * the TF_SHELL_USES test is a kludge that lets us know if + * if the signals have been changed by the shell. + */ + for (i = NELEM(tt_sigs); --i >= 0; ) { + sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES; + /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */ + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + } + } + + /* j_change() calls tty_init() */ + if (Flag(FMONITOR)) + j_change(); + else +#endif + if (Flag(FTALKING)) + tty_init(true, true); +} + +/* job cleanup before shell exit */ +void +j_exit(void) +{ + /* kill stopped, and possibly running, jobs */ + Job *j; + int killed = 0; + + for (j = job_list; j != NULL; j = j->next) { + if (j->ppid == procpid && + (j->state == PSTOPPED || + (j->state == PRUNNING && + ((j->flags & JF_FG) || + (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) { + killed = 1; + if (j->pgrp == 0) + kill_job(j, SIGHUP); + else + mksh_killpg(j->pgrp, SIGHUP); +#ifndef MKSH_UNEMPLOYED + if (j->state == PSTOPPED) { + if (j->pgrp == 0) + kill_job(j, SIGCONT); + else + mksh_killpg(j->pgrp, SIGCONT); + } +#endif + } + } + if (killed) + sleep(1); + j_notify(); + +#ifndef MKSH_UNEMPLOYED + if (kshpid == procpid && restore_ttypgrp >= 0) { + /* + * Need to restore the tty pgrp to what it was when the + * shell started up, so that the process that started us + * will be able to access the tty when we are done. + * Also need to restore our process group in case we are + * about to do an exec so that both our parent and the + * process we are to become will be able to access the tty. + */ + tcsetpgrp(tty_fd, restore_ttypgrp); + setpgid(0, restore_ttypgrp); + } + if (Flag(FMONITOR)) { + Flag(FMONITOR) = 0; + j_change(); + } +#endif +} + +#ifndef MKSH_UNEMPLOYED +/* turn job control on or off according to Flag(FMONITOR) */ +void +j_change(void) +{ + int i; + + if (Flag(FMONITOR)) { + bool use_tty = Flag(FTALKING); + + /* Don't call tcgetattr() 'til we own the tty process group */ + if (use_tty) + tty_init(false, true); + + /* no controlling tty, no SIGT* */ + if ((ttypgrp_ok = use_tty && tty_fd >= 0 && tty_devtty)) { + setsig(&sigtraps[SIGTTIN], SIG_DFL, + SS_RESTORE_ORIG|SS_FORCE); + /* wait to be given tty (POSIX.1, B.2, job control) */ + while (1) { + pid_t ttypgrp; + + if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { + warningf(false, + "j_init: tcgetpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = false; + break; + } + if (ttypgrp == kshpgrp) + break; + kill(0, SIGTTIN); + } + } + for (i = NELEM(tt_sigs); --i >= 0; ) + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_DFL|SS_FORCE); + if (ttypgrp_ok && kshpgrp != kshpid) { + if (setpgid(0, kshpid) < 0) { + warningf(false, + "j_init: setpgid() failed: %s", + strerror(errno)); + ttypgrp_ok = false; + } else { + if (tcsetpgrp(tty_fd, kshpid) < 0) { + warningf(false, + "j_init: tcsetpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = false; + } else + restore_ttypgrp = kshpgrp; + kshpgrp = kshpid; + } + } + if (use_tty && !ttypgrp_ok) + warningf(false, "warning: won't have full job control"); + if (tty_fd >= 0) + tcgetattr(tty_fd, &tty_state); + } else { + ttypgrp_ok = false; + if (Flag(FTALKING)) + for (i = NELEM(tt_sigs); --i >= 0; ) + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + else + for (i = NELEM(tt_sigs); --i >= 0; ) { + if (sigtraps[tt_sigs[i]].flags & + (TF_ORIG_IGN | TF_ORIG_DFL)) + setsig(&sigtraps[tt_sigs[i]], + (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ? + SIG_IGN : SIG_DFL, + SS_RESTORE_ORIG|SS_FORCE); + } + if (!Flag(FTALKING)) + tty_close(); + } +} +#endif + +/* execute tree in child subprocess */ +int +exchild(struct op *t, int flags, + volatile int *xerrok, + /* used if XPCLOSE or XCCLOSE */ int close_fd) +{ + static Proc *last_proc; /* for pipelines */ + + int rv = 0, forksleep; + sigset_t omask; + struct { + Proc *p; + Job *j; + pid_t cldpid; + } pi; + + if (flags & XEXEC) + /* + * Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND + * (also done in another execute() below) + */ + return (execute(t, flags & (XEXEC | XERROK), xerrok)); + + /* no SIGCHLDs while messing with job and process lists */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + pi.p = new_proc(); + pi.p->next = NULL; + pi.p->state = PRUNNING; + pi.p->status = 0; + pi.p->pid = 0; + + /* link process into jobs list */ + if (flags & XPIPEI) { + /* continuing with a pipe */ + if (!last_job) + internal_errorf( + "exchild: XPIPEI and no last_job - pid %d", + (int)procpid); + pi.j = last_job; + if (last_proc) + last_proc->next = pi.p; + last_proc = pi.p; + } else { + pi.j = new_job(); /* fills in pi.j->job */ + /* + * we don't consider XXCOMs foreground since they don't get + * tty process group and we don't save or restore tty modes. + */ + pi.j->flags = (flags & XXCOM) ? JF_XXCOM : + ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE)); + timerclear(&pi.j->usrtime); + timerclear(&pi.j->systime); + pi.j->state = PRUNNING; + pi.j->pgrp = 0; + pi.j->ppid = procpid; + pi.j->age = ++njobs; + pi.j->proc_list = pi.p; + pi.j->coproc_id = 0; + last_job = pi.j; + last_proc = pi.p; + put_job(pi.j, PJ_PAST_STOPPED); + } + + snptreef(pi.p->command, sizeof(pi.p->command), "%T", t); + + /* create child process */ + forksleep = 1; + while ((pi.cldpid = fork()) < 0 && errno == EAGAIN && forksleep < 32) { + if (intrsig) /* allow user to ^C out... */ + break; + sleep(forksleep); + forksleep <<= 1; + } + if (pi.cldpid < 0) { + kill_job(pi.j, SIGKILL); + remove_job(pi.j, "fork failed"); + sigprocmask(SIG_SETMASK, &omask, NULL); + errorf("cannot fork - try again"); + } + pi.p->pid = pi.cldpid ? pi.cldpid : (procpid = getpid()); + + /* + * ensure next child gets a (slightly) different $RANDOM sequence + * from its parent process and other child processes + */ + change_random(&pi, sizeof(pi)); + +#ifndef MKSH_UNEMPLOYED + /* job control set up */ + if (Flag(FMONITOR) && !(flags&XXCOM)) { + int dotty = 0; + if (pi.j->pgrp == 0) { /* First process */ + pi.j->pgrp = pi.p->pid; + dotty = 1; + } + + /* set pgrp in both parent and child to deal with race + * condition + */ + setpgid(pi.p->pid, pi.j->pgrp); + if (ttypgrp_ok && dotty && !(flags & XBGND)) + tcsetpgrp(tty_fd, pi.j->pgrp); + } +#endif + + /* used to close pipe input fd */ + if (close_fd >= 0 && (((flags & XPCLOSE) && pi.cldpid) || + ((flags & XCCLOSE) && !pi.cldpid))) + close(close_fd); + if (!pi.cldpid) { + /* child */ + + /* Do this before restoring signal */ + if (flags & XCOPROC) + coproc_cleanup(false); + sigprocmask(SIG_SETMASK, &omask, NULL); + cleanup_parents_env(); +#ifndef MKSH_UNEMPLOYED + /* If FMONITOR or FTALKING is set, these signals are ignored, + * if neither FMONITOR nor FTALKING are set, the signals have + * their inherited values. + */ + if (Flag(FMONITOR) && !(flags & XXCOM)) { + for (forksleep = NELEM(tt_sigs); --forksleep >= 0; ) + setsig(&sigtraps[tt_sigs[forksleep]], SIG_DFL, + SS_RESTORE_DFL|SS_FORCE); + } +#endif +#if HAVE_NICE + if (Flag(FBGNICE) && (flags & XBGND)) + (void)nice(4); +#endif + if ((flags & XBGND) +#ifndef MKSH_UNEMPLOYED + && !Flag(FMONITOR) +#endif + ) { + setsig(&sigtraps[SIGINT], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + setsig(&sigtraps[SIGQUIT], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + if ((!(flags & (XPIPEI | XCOPROC))) && + ((forksleep = open("/dev/null", 0)) > 0)) { + (void)ksh_dup2(forksleep, 0, true); + close(forksleep); + } + } + remove_job(pi.j, "child"); /* in case of $(jobs) command */ + nzombie = 0; +#ifndef MKSH_UNEMPLOYED + ttypgrp_ok = false; + Flag(FMONITOR) = 0; +#endif + Flag(FTALKING) = 0; + tty_close(); + cleartraps(); + /* no return */ + execute(t, (flags & XERROK) | XEXEC, NULL); +#ifndef MKSH_SMALL + if (t->type == TPIPE) + unwind(LLEAVE); + internal_warningf("exchild: execute() returned"); + fptreef(shl_out, 2, "exchild: tried to execute {\n%T\n}\n", t); + shf_flush(shl_out); +#endif + unwind(LLEAVE); + /* NOTREACHED */ + } + + /* shell (parent) stuff */ + if (!(flags & XPIPEO)) { /* last process in a job */ + j_startjob(pi.j); + if (flags & XCOPROC) { + pi.j->coproc_id = coproc.id; + /* n jobs using co-process output */ + coproc.njobs++; + /* j using co-process input */ + coproc.job = (void *)pi.j; + } + if (flags & XBGND) { + j_set_async(pi.j); + if (Flag(FTALKING)) { + shf_fprintf(shl_out, "[%d]", pi.j->job); + for (pi.p = pi.j->proc_list; pi.p; + pi.p = pi.p->next) + shf_fprintf(shl_out, " %d", + (int)pi.p->pid); + shf_putchar('\n', shl_out); + shf_flush(shl_out); + } + } else + rv = j_waitj(pi.j, JW_NONE, "jw:last proc"); + } + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return (rv); +} + +/* start the last job: only used for $(command) jobs */ +void +startlast(void) +{ + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (last_job) { /* no need to report error - waitlast() will do it */ + /* ensure it isn't removed by check_job() */ + last_job->flags |= JF_WAITING; + j_startjob(last_job); + } + sigprocmask(SIG_SETMASK, &omask, NULL); +} + +/* wait for last job: only used for $(command) jobs */ +int +waitlast(void) +{ + int rv; + Job *j; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + j = last_job; + if (!j || !(j->flags & JF_STARTED)) { + if (!j) + warningf(true, "waitlast: no last job"); + else + internal_warningf("waitlast: not started"); + sigprocmask(SIG_SETMASK, &omask, NULL); + return (125); /* not so arbitrary, non-zero value */ + } + + rv = j_waitj(j, JW_NONE, "jw:waitlast"); + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return (rv); +} + +/* wait for child, interruptable. */ +int +waitfor(const char *cp, int *sigp) +{ + int rv; + Job *j; + int ecode; + int flags = JW_INTERRUPT|JW_ASYNCNOTIFY; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + *sigp = 0; + + if (cp == NULL) { + /* + * wait for an unspecified job - always returns 0, so + * don't have to worry about exited/signaled jobs + */ + for (j = job_list; j; j = j->next) + /* AT&T ksh will wait for stopped jobs - we don't */ + if (j->ppid == procpid && j->state == PRUNNING) + break; + if (!j) { + sigprocmask(SIG_SETMASK, &omask, NULL); + return (-1); + } + } else if ((j = j_lookup(cp, &ecode))) { + /* don't report normal job completion */ + flags &= ~JW_ASYNCNOTIFY; + if (j->ppid != procpid) { + sigprocmask(SIG_SETMASK, &omask, NULL); + return (-1); + } + } else { + sigprocmask(SIG_SETMASK, &omask, NULL); + if (ecode != JL_NOSUCH) + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return (-1); + } + + /* AT&T ksh will wait for stopped jobs - we don't */ + rv = j_waitj(j, flags, "jw:waitfor"); + + sigprocmask(SIG_SETMASK, &omask, NULL); + + if (rv < 0) /* we were interrupted */ + *sigp = 128 + -rv; + + return (rv); +} + +/* kill (built-in) a job */ +int +j_kill(const char *cp, int sig) +{ + Job *j; + int rv = 0; + int ecode; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return (1); + } + + if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */ + if (kill_job(j, sig) < 0) { + bi_errorf("%s: %s", cp, strerror(errno)); + rv = 1; + } + } else { +#ifndef MKSH_UNEMPLOYED + if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP)) + mksh_killpg(j->pgrp, SIGCONT); +#endif + if (mksh_killpg(j->pgrp, sig) < 0) { + bi_errorf("%s: %s", cp, strerror(errno)); + rv = 1; + } + } + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return (rv); +} + +#ifndef MKSH_UNEMPLOYED +/* fg and bg built-ins: called only if Flag(FMONITOR) set */ +int +j_resume(const char *cp, int bg) +{ + Job *j; + Proc *p; + int ecode; + int running; + int rv = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return (1); + } + + if (j->pgrp == 0) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("job not job-controlled"); + return (1); + } + + if (bg) + shprintf("[%d] ", j->job); + + running = 0; + for (p = j->proc_list; p != NULL; p = p->next) { + if (p->state == PSTOPPED) { + p->state = PRUNNING; + p->status = 0; + running = 1; + } + shf_puts(p->command, shl_stdout); + if (p->next) + shf_puts("| ", shl_stdout); + } + shf_putc('\n', shl_stdout); + shf_flush(shl_stdout); + if (running) + j->state = PRUNNING; + + put_job(j, PJ_PAST_STOPPED); + if (bg) + j_set_async(j); + else { + /* attach tty to job */ + if (j->state == PRUNNING) { + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) + tcsetattr(tty_fd, TCSADRAIN, &j->ttystate); + /* See comment in j_waitj regarding saved_ttypgrp. */ + if (ttypgrp_ok && + tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ? + j->saved_ttypgrp : j->pgrp) < 0) { + rv = errno; + if (j->flags & JF_SAVEDTTY) + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + sigprocmask(SIG_SETMASK, &omask, + NULL); + bi_errorf("1st tcsetpgrp(%d, %d) failed: %s", + tty_fd, + (int)((j->flags & JF_SAVEDTTYPGRP) ? + j->saved_ttypgrp : j->pgrp), + strerror(rv)); + return (1); + } + } + j->flags |= JF_FG; + j->flags &= ~JF_KNOWN; + if (j == async_job) + async_job = NULL; + } + + if (j->state == PRUNNING && mksh_killpg(j->pgrp, SIGCONT) < 0) { + int err = errno; + + if (!bg) { + j->flags &= ~JF_FG; + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + if (ttypgrp_ok && tcsetpgrp(tty_fd, kshpgrp) < 0) + warningf(true, + "fg: 2nd tcsetpgrp(%d, %ld) failed: %s", + tty_fd, (long)kshpgrp, strerror(errno)); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("cannot continue job %s: %s", + cp, strerror(err)); + return (1); + } + if (!bg) { + if (ttypgrp_ok) { + j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP); + } + rv = j_waitj(j, JW_NONE, "jw:resume"); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return (rv); +} +#endif + +/* are there any running or stopped jobs ? */ +int +j_stopped_running(void) +{ + Job *j; + int which = 0; + + for (j = job_list; j != NULL; j = j->next) { +#ifndef MKSH_UNEMPLOYED + if (j->ppid == procpid && j->state == PSTOPPED) + which |= 1; +#endif + if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid && + j->ppid == procpid && j->state == PRUNNING) + which |= 2; + } + if (which) { + shellf("You have %s%s%s jobs\n", + which & 1 ? "stopped" : "", + which == 3 ? " and " : "", + which & 2 ? "running" : ""); + return (1); + } + + return (0); +} + +int +j_njobs(void) +{ + Job *j; + int nj = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + for (j = job_list; j; j = j->next) + nj++; + + sigprocmask(SIG_SETMASK, &omask, NULL); + return (nj); +} + + +/* list jobs for jobs built-in */ +int +j_jobs(const char *cp, int slp, + int nflag) /* 0: short, 1: long, 2: pgrp */ +{ + Job *j, *tmp; + int how; + int zflag = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (nflag < 0) { /* kludge: print zombies */ + nflag = 0; + zflag = 1; + } + if (cp) { + int ecode; + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return (1); + } + } else + j = job_list; + how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP); + for (; j; j = j->next) { + if ((!(j->flags & JF_ZOMBIE) || zflag) && + (!nflag || (j->flags & JF_CHANGED))) { + j_print(j, how, shl_stdout); + if (j->state == PEXITED || j->state == PSIGNALLED) + j->flags |= JF_REMOVE; + } + if (cp) + break; + } + /* Remove jobs after printing so there won't be multiple + or - jobs */ + for (j = job_list; j; j = tmp) { + tmp = j->next; + if (j->flags & JF_REMOVE) + remove_job(j, "jobs"); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return (0); +} + +/* list jobs for top-level notification */ +void +j_notify(void) +{ + Job *j, *tmp; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + for (j = job_list; j; j = j->next) { +#ifndef MKSH_UNEMPLOYED + if (Flag(FMONITOR) && (j->flags & JF_CHANGED)) + j_print(j, JP_MEDIUM, shl_out); +#endif + /* Remove job after doing reports so there aren't + * multiple +/- jobs. + */ + if (j->state == PEXITED || j->state == PSIGNALLED) + j->flags |= JF_REMOVE; + } + for (j = job_list; j; j = tmp) { + tmp = j->next; + if (j->flags & JF_REMOVE) + remove_job(j, "notify"); + } + shf_flush(shl_out); + sigprocmask(SIG_SETMASK, &omask, NULL); +} + +/* Return pid of last process in last asynchronous job */ +pid_t +j_async(void) +{ + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (async_job) + async_job->flags |= JF_KNOWN; + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return (async_pid); +} + +/* + * Make j the last async process + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_set_async(Job *j) +{ + Job *jl, *oldest; + + if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE) + remove_job(async_job, "async"); + if (!(j->flags & JF_STARTED)) { + internal_warningf("j_async: job not started"); + return; + } + async_job = j; + async_pid = j->last_proc->pid; + while (nzombie > CHILD_MAX) { + oldest = NULL; + for (jl = job_list; jl; jl = jl->next) + if (jl != async_job && (jl->flags & JF_ZOMBIE) && + (!oldest || jl->age < oldest->age)) + oldest = jl; + if (!oldest) { + /* XXX debugging */ + if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) { + internal_warningf("j_async: bad nzombie (%d)", + nzombie); + nzombie = 0; + } + break; + } + remove_job(oldest, "zombie"); + } +} + +/* + * Start a job: set STARTED, check for held signals and set j->last_proc + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_startjob(Job *j) +{ + Proc *p; + + j->flags |= JF_STARTED; + for (p = j->proc_list; p->next; p = p->next) + ; + j->last_proc = p; + + if (held_sigchld) { + held_sigchld = 0; + /* Don't call j_sigchld() as it may remove job... */ + kill(procpid, SIGCHLD); + } +} + +/* + * wait for job to complete or change state + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static int +j_waitj(Job *j, + int flags, /* see JW_* */ + const char *where) +{ + int rv; + + /* + * No auto-notify on the job we are waiting on. + */ + j->flags |= JF_WAITING; + if (flags & JW_ASYNCNOTIFY) + j->flags |= JF_W_ASYNCNOTIFY; + +#ifndef MKSH_UNEMPLOYED + if (!Flag(FMONITOR)) +#endif + flags |= JW_STOPPEDWAIT; + + while (j->state == PRUNNING || + ((flags & JW_STOPPEDWAIT) && j->state == PSTOPPED)) { + sigsuspend(&sm_default); + if (fatal_trap) { + int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + runtraps(TF_FATAL); + j->flags |= oldf; /* not reached... */ + } + if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + return (-rv); + } + } + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + + if (j->flags & JF_FG) { + j->flags &= ~JF_FG; +#ifndef MKSH_UNEMPLOYED + if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) { + /* + * Save the tty's current pgrp so it can be restored + * when the job is foregrounded. This is to + * deal with things like the GNU su which does + * a fork/exec instead of an exec (the fork means + * the execed shell gets a different pid from its + * pgrp, so naturally it sets its pgrp and gets hosed + * when it gets foregrounded by the parent shell which + * has restored the tty's pgrp to that of the su + * process). + */ + if (j->state == PSTOPPED && + (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0) + j->flags |= JF_SAVEDTTYPGRP; + if (tcsetpgrp(tty_fd, kshpgrp) < 0) + warningf(true, + "j_waitj: tcsetpgrp(%d, %ld) failed: %s", + tty_fd, (long)kshpgrp, strerror(errno)); + if (j->state == PSTOPPED) { + j->flags |= JF_SAVEDTTY; + tcgetattr(tty_fd, &j->ttystate); + } + } +#endif + if (tty_fd >= 0) { + /* + * Only restore tty settings if job was originally + * started in the foreground. Problems can be + * caused by things like 'more foobar &' which will + * typically get and save the shell's vi/emacs tty + * settings before setting up the tty for itself; + * when more exits, it restores the 'original' + * settings, and things go down hill from there... + */ + if (j->state == PEXITED && j->status == 0 && + (j->flags & JF_USETTYMODE)) { + tcgetattr(tty_fd, &tty_state); + } else { + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + /*- + * Don't use tty mode if job is stopped and + * later restarted and exits. Consider + * the sequence: + * vi foo (stopped) + * ... + * stty something + * ... + * fg (vi; ZZ) + * mode should be that of the stty, not what + * was before the vi started. + */ + if (j->state == PSTOPPED) + j->flags &= ~JF_USETTYMODE; + } + } +#ifndef MKSH_UNEMPLOYED + /* + * If it looks like user hit ^C to kill a job, pretend we got + * one too to break out of for loops, etc. (AT&T ksh does this + * even when not monitoring, but this doesn't make sense since + * a tty generated ^C goes to the whole process group) + */ + { + int status; + + status = j->last_proc->status; + if (Flag(FMONITOR) && j->state == PSIGNALLED && + WIFSIGNALED(status) && + (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR)) + trapsig(WTERMSIG(status)); + } +#endif + } + + j_usrtime = j->usrtime; + j_systime = j->systime; + rv = j->status; + + if (!(flags & JW_ASYNCNOTIFY) +#ifndef MKSH_UNEMPLOYED + && (!Flag(FMONITOR) || j->state != PSTOPPED) +#endif + ) { + j_print(j, JP_SHORT, shl_out); + shf_flush(shl_out); + } + if (j->state != PSTOPPED +#ifndef MKSH_UNEMPLOYED + && (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY)) +#endif + ) + remove_job(j, where); + + return (rv); +} + +/* + * SIGCHLD handler to reap children and update job states + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +/* ARGSUSED */ +static void +j_sigchld(int sig MKSH_A_UNUSED) +{ + /* this runs inside interrupt context, with errno saved */ + + Job *j; + Proc *p = NULL; + pid_t pid; + int status; + struct rusage ru0, ru1; + + /* + * Don't wait for any processes if a job is partially started. + * This is so we don't do away with the process group leader + * before all the processes in a pipe line are started (so the + * setpgid() won't fail) + */ + for (j = job_list; j; j = j->next) + if (j->ppid == procpid && !(j->flags & JF_STARTED)) { + held_sigchld = 1; + return; + } + + getrusage(RUSAGE_CHILDREN, &ru0); + do { + pid = waitpid(-1, &status, (WNOHANG|WUNTRACED)); + + /* + * return if this would block (0) or no children + * or interrupted (-1) + */ + if (pid <= 0) + return; + + getrusage(RUSAGE_CHILDREN, &ru1); + + /* find job and process structures for this pid */ + for (j = job_list; j != NULL; j = j->next) + for (p = j->proc_list; p != NULL; p = p->next) + if (p->pid == pid) + goto found; + found: + if (j == NULL) { + /* Can occur if process has kids, then execs shell + warningf(true, "bad process waited for (pid = %d)", + pid); + */ + ru0 = ru1; + continue; + } + + timeradd(&j->usrtime, &ru1.ru_utime, &j->usrtime); + timersub(&j->usrtime, &ru0.ru_utime, &j->usrtime); + timeradd(&j->systime, &ru1.ru_stime, &j->systime); + timersub(&j->systime, &ru0.ru_stime, &j->systime); + ru0 = ru1; + p->status = status; +#ifndef MKSH_UNEMPLOYED + if (WIFSTOPPED(status)) + p->state = PSTOPPED; + else +#endif + if (WIFSIGNALED(status)) + p->state = PSIGNALLED; + else + p->state = PEXITED; + + check_job(j); /* check to see if entire job is done */ + } while (1); +} + +/* + * Called only when a process in j has exited/stopped (ie, called only + * from j_sigchld()). If no processes are running, the job status + * and state are updated, asynchronous job notification is done and, + * if unneeded, the job is removed. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +check_job(Job *j) +{ + int jstate; + Proc *p; + + /* XXX debugging (nasty - interrupt routine using shl_out) */ + if (!(j->flags & JF_STARTED)) { + internal_warningf("check_job: job started (flags 0x%x)", + j->flags); + return; + } + + jstate = PRUNNING; + for (p=j->proc_list; p != NULL; p = p->next) { + if (p->state == PRUNNING) + return; /* some processes still running */ + if (p->state > jstate) + jstate = p->state; + } + j->state = jstate; + + switch (j->last_proc->state) { + case PEXITED: + j->status = WEXITSTATUS(j->last_proc->status); + break; + case PSIGNALLED: + j->status = 128 + WTERMSIG(j->last_proc->status); + break; + default: + j->status = 0; + break; + } + + /* + * Note when co-process dies: can't be done in j_wait() nor + * remove_job() since neither may be called for non-interactive + * shells. + */ + if (j->state == PEXITED || j->state == PSIGNALLED) { + /* + * No need to keep co-process input any more + * (at least, this is what ksh93d thinks) + */ + if (coproc.job == j) { + coproc.job = NULL; + /* + * XXX would be nice to get the closes out of here + * so they aren't done in the signal handler. + * Would mean a check in coproc_getfd() to + * do "if job == 0 && write >= 0, close write". + */ + coproc_write_close(coproc.write); + } + /* Do we need to keep the output? */ + if (j->coproc_id && j->coproc_id == coproc.id && + --coproc.njobs == 0) + coproc_readw_close(coproc.read); + } + + j->flags |= JF_CHANGED; +#ifndef MKSH_UNEMPLOYED + if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) { + /* + * Only put stopped jobs at the front to avoid confusing + * the user (don't want finished jobs effecting %+ or %-) + */ + if (j->state == PSTOPPED) + put_job(j, PJ_ON_FRONT); + if (Flag(FNOTIFY) && + (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) { + /* Look for the real file descriptor 2 */ + { + struct env *ep; + int fd = 2; + + for (ep = e; ep; ep = ep->oenv) + if (ep->savefd && ep->savefd[2]) + fd = ep->savefd[2]; + shf_reopen(fd, SHF_WR, shl_j); + } + /* + * Can't call j_notify() as it removes jobs. The job + * must stay in the job list as j_waitj() may be + * running with this job. + */ + j_print(j, JP_MEDIUM, shl_j); + shf_flush(shl_j); + if (!(j->flags & JF_WAITING) && j->state != PSTOPPED) + remove_job(j, "notify"); + } + } +#endif + if ( +#ifndef MKSH_UNEMPLOYED + !Flag(FMONITOR) && +#endif + !(j->flags & (JF_WAITING|JF_FG)) && + j->state != PSTOPPED) { + if (j == async_job || (j->flags & JF_KNOWN)) { + j->flags |= JF_ZOMBIE; + j->job = -1; + nzombie++; + } else + remove_job(j, "checkjob"); + } +} + +/* + * Print job status in either short, medium or long format. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_print(Job *j, int how, struct shf *shf) +{ + Proc *p; + int state; + int status; + int coredumped; + char jobchar = ' '; + char buf[64]; + const char *filler; + int output = 0; + + if (how == JP_PGRP) { + /* + * POSIX doesn't say what to do it there is no process + * group leader (ie, !FMONITOR). We arbitrarily return + * last pid (which is what $! returns). + */ + shf_fprintf(shf, "%d\n", (int)(j->pgrp ? j->pgrp : + (j->last_proc ? j->last_proc->pid : 0))); + return; + } + j->flags &= ~JF_CHANGED; + filler = j->job > 10 ? "\n " : "\n "; + if (j == job_list) + jobchar = '+'; + else if (j == job_list->next) + jobchar = '-'; + + for (p = j->proc_list; p != NULL;) { + coredumped = 0; + switch (p->state) { + case PRUNNING: + memcpy(buf, "Running", 8); + break; + case PSTOPPED: + strlcpy(buf, sigtraps[WSTOPSIG(p->status)].mess, + sizeof(buf)); + break; + case PEXITED: + if (how == JP_SHORT) + buf[0] = '\0'; + else if (WEXITSTATUS(p->status) == 0) + memcpy(buf, "Done", 5); + else + shf_snprintf(buf, sizeof(buf), "Done (%d)", + WEXITSTATUS(p->status)); + break; + case PSIGNALLED: +#ifdef WCOREDUMP + if (WCOREDUMP(p->status)) + coredumped = 1; +#endif + /* + * kludge for not reporting 'normal termination + * signals' (i.e. SIGINT, SIGPIPE) + */ + if (how == JP_SHORT && !coredumped && + (WTERMSIG(p->status) == SIGINT || + WTERMSIG(p->status) == SIGPIPE)) { + buf[0] = '\0'; + } else + strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess, + sizeof(buf)); + break; + } + + if (how != JP_SHORT) { + if (p == j->proc_list) + shf_fprintf(shf, "[%d] %c ", j->job, jobchar); + else + shf_fprintf(shf, "%s", filler); + } + + if (how == JP_LONG) + shf_fprintf(shf, "%5d ", (int)p->pid); + + if (how == JP_SHORT) { + if (buf[0]) { + output = 1; + shf_fprintf(shf, "%s%s ", + buf, coredumped ? " (core dumped)" : null); + } + } else { + output = 1; + shf_fprintf(shf, "%-20s %s%s%s", buf, p->command, + p->next ? "|" : null, + coredumped ? " (core dumped)" : null); + } + + state = p->state; + status = p->status; + p = p->next; + while (p && p->state == state && p->status == status) { + if (how == JP_LONG) + shf_fprintf(shf, "%s%5d %-20s %s%s", filler, + (int)p->pid, " ", p->command, + p->next ? "|" : null); + else if (how == JP_MEDIUM) + shf_fprintf(shf, " %s%s", p->command, + p->next ? "|" : null); + p = p->next; + } + } + if (output) + shf_putc('\n', shf); +} + +/* + * Convert % sequence to job + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Job * +j_lookup(const char *cp, int *ecodep) +{ + Job *j, *last_match; + Proc *p; + int len, job = 0; + + if (ksh_isdigit(*cp)) { + getn(cp, &job); + /* Look for last_proc->pid (what $! returns) first... */ + for (j = job_list; j != NULL; j = j->next) + if (j->last_proc && j->last_proc->pid == job) + return (j); + /* + * ...then look for process group (this is non-POSIX, + * but should not break anything + */ + for (j = job_list; j != NULL; j = j->next) + if (j->pgrp && j->pgrp == job) + return (j); + if (ecodep) + *ecodep = JL_NOSUCH; + return (NULL); + } + if (*cp != '%') { + if (ecodep) + *ecodep = JL_INVALID; + return (NULL); + } + switch (*++cp) { + case '\0': /* non-standard */ + case '+': + case '%': + if (job_list != NULL) + return (job_list); + break; + + case '-': + if (job_list != NULL && job_list->next) + return (job_list->next); + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + getn(cp, &job); + for (j = job_list; j != NULL; j = j->next) + if (j->job == job) + return (j); + break; + + case '?': /* %?string */ + last_match = NULL; + for (j = job_list; j != NULL; j = j->next) + for (p = j->proc_list; p != NULL; p = p->next) + if (strstr(p->command, cp+1) != NULL) { + if (last_match) { + if (ecodep) + *ecodep = JL_AMBIG; + return (NULL); + } + last_match = j; + } + if (last_match) + return (last_match); + break; + + default: /* %string */ + len = strlen(cp); + last_match = NULL; + for (j = job_list; j != NULL; j = j->next) + if (strncmp(cp, j->proc_list->command, len) == 0) { + if (last_match) { + if (ecodep) + *ecodep = JL_AMBIG; + return (NULL); + } + last_match = j; + } + if (last_match) + return (last_match); + break; + } + if (ecodep) + *ecodep = JL_NOSUCH; + return (NULL); +} + +static Job *free_jobs; +static Proc *free_procs; + +/* + * allocate a new job and fill in the job number. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Job * +new_job(void) +{ + int i; + Job *newj, *j; + + if (free_jobs != NULL) { + newj = free_jobs; + free_jobs = free_jobs->next; + } else + newj = alloc(sizeof(Job), APERM); + + /* brute force method */ + for (i = 1; ; i++) { + for (j = job_list; j && j->job != i; j = j->next) + ; + if (j == NULL) + break; + } + newj->job = i; + + return (newj); +} + +/* + * Allocate new process struct + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Proc * +new_proc(void) +{ + Proc *p; + + if (free_procs != NULL) { + p = free_procs; + free_procs = free_procs->next; + } else + p = alloc(sizeof(Proc), APERM); + + return (p); +} + +/* + * Take job out of job_list and put old structures into free list. + * Keeps nzombies, last_job and async_job up to date. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +remove_job(Job *j, const char *where) +{ + Proc *p, *tmp; + Job **prev, *curr; + + prev = &job_list; + curr = *prev; + for (; curr != NULL && curr != j; prev = &curr->next, curr = *prev) + ; + if (curr != j) { + internal_warningf("remove_job: job not found (%s)", where); + return; + } + *prev = curr->next; + + /* free up proc structures */ + for (p = j->proc_list; p != NULL; ) { + tmp = p; + p = p->next; + tmp->next = free_procs; + free_procs = tmp; + } + + if ((j->flags & JF_ZOMBIE) && j->ppid == procpid) + --nzombie; + j->next = free_jobs; + free_jobs = j; + + if (j == last_job) + last_job = NULL; + if (j == async_job) + async_job = NULL; +} + +/* + * put j in a particular location (taking it out job_list if it is there + * already) + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +put_job(Job *j, int where) +{ + Job **prev, *curr; + + /* Remove job from list (if there) */ + prev = &job_list; + curr = job_list; + for (; curr && curr != j; prev = &curr->next, curr = *prev) + ; + if (curr == j) + *prev = curr->next; + + switch (where) { + case PJ_ON_FRONT: + j->next = job_list; + job_list = j; + break; + + case PJ_PAST_STOPPED: + prev = &job_list; + curr = job_list; + for (; curr && curr->state == PSTOPPED; prev = &curr->next, + curr = *prev) + ; + j->next = curr; + *prev = j; + break; + } +} + +/* + * nuke a job (called when unable to start full job). + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static int +kill_job(Job *j, int sig) +{ + Proc *p; + int rval = 0; + + for (p = j->proc_list; p != NULL; p = p->next) + if (p->pid != 0) + if (kill(p->pid, sig) < 0) + rval = -1; + return (rval); +} diff --git a/mksh/src/lalloc.c b/mksh/src/lalloc.c new file mode 100644 index 0000000..79627d1 --- /dev/null +++ b/mksh/src/lalloc.c @@ -0,0 +1,123 @@ +/*- + * Copyright © 2009 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un‐ + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person’s immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/lalloc.c,v 1.11 2009/08/08 13:08:51 tg Exp $"); + +/* build with CPPFLAGS+= -DUSE_REALLOC_MALLOC=0 on ancient systems */ +#if defined(USE_REALLOC_MALLOC) && (USE_REALLOC_MALLOC == 0) +#define remalloc(p,n) ((p) == NULL ? malloc(n) : realloc((p), (n))) +#else +#define remalloc(p,n) realloc((p), (n)) +#endif + +#define ALLOC_ISUNALIGNED(p) (((ptrdiff_t)(p)) % ALLOC_SIZE) + +static ALLOC_ITEM *findptr(ALLOC_ITEM **, char *, Area *); + +void +ainit(Area *ap) +{ + /* area pointer is an ALLOC_ITEM, just the head of the list */ + ap->next = NULL; +} + +static ALLOC_ITEM * +findptr(ALLOC_ITEM **lpp, char *ptr, Area *ap) +{ + void *lp; + +#ifndef MKSH_SMALL + if (ALLOC_ISUNALIGNED(ptr)) + goto fail; +#endif + /* get address of ALLOC_ITEM from user item */ + /* + * note: the alignment of "ptr" to ALLOC_SIZE is checked + * above; the "void *" gets us rid of a gcc 2.95 warning + */ + *lpp = (lp = ptr - ALLOC_SIZE); + /* search for allocation item in group list */ + while (ap->next != lp) + if ((ap = ap->next) == NULL) { +#ifndef MKSH_SMALL + fail: +#endif + internal_errorf("rogue pointer %p", ptr); + } + return (ap); +} + +void * +aresize(void *ptr, size_t numb, Area *ap) +{ + ALLOC_ITEM *lp = NULL; + + /* resizing (true) or newly allocating? */ + if (ptr != NULL) { + ALLOC_ITEM *pp; + + pp = findptr(&lp, ptr, ap); + pp->next = lp->next; + } + + if ((numb >= SIZE_MAX - ALLOC_SIZE) || + (lp = remalloc(lp, numb + ALLOC_SIZE)) == NULL +#ifndef MKSH_SMALL + || ALLOC_ISUNALIGNED(lp) +#endif + ) + internal_errorf("cannot allocate %lu data bytes", + (unsigned long)numb); + /* this only works because Area is an ALLOC_ITEM */ + lp->next = ap->next; + ap->next = lp; + /* return user item address */ + return ((char *)lp + ALLOC_SIZE); +} + +void +afree(void *ptr, Area *ap) +{ + if (ptr != NULL) { + ALLOC_ITEM *lp, *pp; + + pp = findptr(&lp, ptr, ap); + /* unhook */ + pp->next = lp->next; + /* now free ALLOC_ITEM */ + free(lp); + } +} + +void +afreeall(Area *ap) +{ + ALLOC_ITEM *lp; + + /* traverse group (linked list) */ + while ((lp = ap->next) != NULL) { + /* make next ALLOC_ITEM head of list */ + ap->next = lp->next; + /* free old head */ + free(lp); + } +} diff --git a/mksh/src/lex.c b/mksh/src/lex.c new file mode 100644 index 0000000..d0219e7 --- /dev/null +++ b/mksh/src/lex.c @@ -0,0 +1,1782 @@ +/* $OpenBSD: lex.c,v 1.44 2008/07/03 17:52:08 otto Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.118 2010/07/25 11:35:41 tg Exp $"); + +/* + * states while lexing word + */ +#define SBASE 0 /* outside any lexical constructs */ +#define SWORD 1 /* implicit quoting for substitute() */ +#define SLETPAREN 2 /* inside (( )), implicit quoting */ +#define SSQUOTE 3 /* inside '' */ +#define SDQUOTE 4 /* inside "" */ +#define SEQUOTE 5 /* inside $'' */ +#define SBRACE 6 /* inside ${} */ +#define SQBRACE 7 /* inside "${}" */ +#define SCSPAREN 8 /* inside $() */ +#define SBQUOTE 9 /* inside `` */ +#define SASPAREN 10 /* inside $(( )) */ +#define SHEREDELIM 11 /* parsing <<,<<- delimiter */ +#define SHEREDQUOTE 12 /* parsing " in <<,<<- delimiter */ +#define SPATTERN 13 /* parsing *(...|...) pattern (*+?@!) */ +#define STBRACE 14 /* parsing ${...[#%]...} */ +#define SLETARRAY 15 /* inside =( ), just copy */ +#define SADELIM 16 /* like SBASE, looking for delimiter */ +#define SHERESTRING 17 /* parsing <<< string */ + +/* Structure to keep track of the lexing state and the various pieces of info + * needed for each particular state. */ +typedef struct lex_state Lex_state; +struct lex_state { + int ls_state; + union { + /* $(...) */ + struct scsparen_info { + int nparen; /* count open parenthesis */ + int csstate; /* XXX remove */ +#define ls_scsparen ls_info.u_scsparen + } u_scsparen; + + /* $((...)) */ + struct sasparen_info { + int nparen; /* count open parenthesis */ + int start; /* marks start of $(( in output str */ +#define ls_sasparen ls_info.u_sasparen + } u_sasparen; + + /* ((...)) */ + struct sletparen_info { + int nparen; /* count open parenthesis */ +#define ls_sletparen ls_info.u_sletparen + } u_sletparen; + + /* `...` */ + struct sbquote_info { + int indquotes; /* true if in double quotes: "`...`" */ +#define ls_sbquote ls_info.u_sbquote + } u_sbquote; + +#ifndef MKSH_SMALL + /* =(...) */ + struct sletarray_info { + int nparen; /* count open parentheses */ +#define ls_sletarray ls_info.u_sletarray + } u_sletarray; +#endif + + /* ADELIM */ + struct sadelim_info { + unsigned char nparen; /* count open parentheses */ +#define SADELIM_BASH 0 +#define SADELIM_MAKE 1 + unsigned char style; + unsigned char delimiter; + unsigned char num; + unsigned char flags; /* ofs. into sadelim_flags[] */ +#define ls_sadelim ls_info.u_sadelim + } u_sadelim; + + /* $'...' */ + struct sequote_info { + bool got_NUL; /* ignore rest of string */ +#define ls_sequote ls_info.u_sequote + } u_sequote; + + Lex_state *base; /* used to point to next state block */ + } ls_info; +}; + +typedef struct { + Lex_state *base; + Lex_state *end; +} State_info; + +static void readhere(struct ioword *); +static int getsc__(void); +static void getsc_line(Source *); +static int getsc_bn(void); +static int s_get(void); +static void s_put(int); +static char *get_brace_var(XString *, char *); +static int arraysub(char **); +static const char *ungetsc(int); +static void gethere(bool); +static Lex_state *push_state_(State_info *, Lex_state *); +static Lex_state *pop_state_(State_info *, Lex_state *); + +static int dopprompt(const char *, int, bool); + +static int backslash_skip; +static int ignore_backslash_newline; + +/* optimised getsc_bn() */ +#define _getsc() (*source->str != '\0' && *source->str != '\\' \ + && !backslash_skip && !(source->flags & SF_FIRST) \ + ? *source->str++ : getsc_bn()) +/* optimised getsc__() */ +#define _getsc_() ((*source->str != '\0') && !(source->flags & SF_FIRST) \ + ? *source->str++ : getsc__()) + +#ifdef MKSH_SMALL +static int getsc(void); +static int getsc_(void); + +static int +getsc(void) +{ + return (_getsc()); +} + +static int +getsc_(void) +{ + return (_getsc_()); +} +#else +/* !MKSH_SMALL: use them inline */ +#define getsc() _getsc() +#define getsc_() _getsc_() +#endif + +#define STATE_BSIZE 32 + +#define PUSH_STATE(s) do { \ + if (++statep == state_info.end) \ + statep = push_state_(&state_info, statep); \ + state = statep->ls_state = (s); \ +} while (0) + +#define POP_STATE() do { \ + if (--statep == state_info.base) \ + statep = pop_state_(&state_info, statep); \ + state = statep->ls_state; \ +} while (0) + +/** + * Lexical analyser + * + * tokens are not regular expressions, they are LL(1). + * for example, "${var:-${PWD}}", and "$(size $(whence ksh))". + * hence the state stack. + */ + +int +yylex(int cf) +{ + Lex_state states[STATE_BSIZE], *statep, *s2, *base; + State_info state_info; + int c, c2, state; + XString ws; /* expandable output word */ + char *wp; /* output word pointer */ + char *sp, *dp; + + Again: + states[0].ls_state = -1; + states[0].ls_info.base = NULL; + statep = &states[1]; + state_info.base = states; + state_info.end = &state_info.base[STATE_BSIZE]; + + Xinit(ws, wp, 64, ATEMP); + + backslash_skip = 0; + ignore_backslash_newline = 0; + + if (cf&ONEWORD) + state = SWORD; + else if (cf&LETEXPR) { + /* enclose arguments in (double) quotes */ + *wp++ = OQUOTE; + state = SLETPAREN; + statep->ls_sletparen.nparen = 0; +#ifndef MKSH_SMALL + } else if (cf&LETARRAY) { + state = SLETARRAY; + statep->ls_sletarray.nparen = 0; +#endif + } else { /* normal lexing */ + state = (cf & HEREDELIM) ? SHEREDELIM : SBASE; + while ((c = getsc()) == ' ' || c == '\t') + ; + if (c == '#') { + ignore_backslash_newline++; + while ((c = getsc()) != '\0' && c != '\n') + ; + ignore_backslash_newline--; + } + ungetsc(c); + } + if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */ + source->flags &= ~SF_ALIAS; + cf |= ALIAS; + } + + /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */ + statep->ls_state = state; + + /* check for here string */ + if (state == SHEREDELIM) { + c = getsc(); + if (c == '<') { + state = SHERESTRING; + while ((c = getsc()) == ' ' || c == '\t') + ; + ungetsc(c); + c = '<'; + goto accept_nonword; + } + ungetsc(c); + } + + /* collect non-special or quoted characters to form word */ + while (!((c = getsc()) == 0 || + ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) && + ctype(c, C_LEX1)))) { + accept_nonword: + Xcheck(ws, wp); + switch (state) { + case SADELIM: + if (c == '(') + statep->ls_sadelim.nparen++; + else if (c == ')') + statep->ls_sadelim.nparen--; + else if (statep->ls_sadelim.nparen == 0 && + (c == /*{*/ '}' || c == statep->ls_sadelim.delimiter)) { + *wp++ = ADELIM; + *wp++ = c; + if (c == /*{*/ '}' || --statep->ls_sadelim.num == 0) + POP_STATE(); + if (c == /*{*/ '}') + POP_STATE(); + break; + } + /* FALLTHROUGH */ + case SBASE: + if (c == '[' && (cf & (VARASN|ARRAYVAR))) { + *wp = EOS; /* temporary */ + if (is_wdvarname(Xstring(ws, wp), false)) { + char *p, *tmp; + + if (arraysub(&tmp)) { + *wp++ = CHAR; + *wp++ = c; + for (p = tmp; *p; ) { + Xcheck(ws, wp); + *wp++ = CHAR; + *wp++ = *p++; + } + afree(tmp, ATEMP); + break; + } else { + Source *s; + + s = pushs(SREREAD, + source->areap); + s->start = s->str = + s->u.freeme = tmp; + s->next = source; + source = s; + } + } + *wp++ = CHAR; + *wp++ = c; + break; + } + /* FALLTHROUGH */ + Sbase1: /* includes *(...|...) pattern (*+?@!) */ + if (c == '*' || c == '@' || c == '+' || c == '?' || + c == '!') { + c2 = getsc(); + if (c2 == '(' /*)*/ ) { + *wp++ = OPAT; + *wp++ = c; + PUSH_STATE(SPATTERN); + break; + } + ungetsc(c2); + } + /* FALLTHROUGH */ + Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ + switch (c) { + case '\\': + getsc_qchar: + if ((c = getsc())) { + /* trailing \ is lost */ + *wp++ = QCHAR; + *wp++ = c; + } + break; + case '\'': + open_ssquote: + *wp++ = OQUOTE; + ignore_backslash_newline++; + PUSH_STATE(SSQUOTE); + break; + case '"': + open_sdquote: + *wp++ = OQUOTE; + PUSH_STATE(SDQUOTE); + break; + default: + goto Subst; + } + break; + + Subst: + switch (c) { + case '\\': + c = getsc(); + switch (c) { + case '"': + if ((cf & HEREDOC)) + goto heredocquote; + /* FALLTHROUGH */ + case '\\': + case '$': case '`': + store_qchar: + *wp++ = QCHAR; + *wp++ = c; + break; + default: + heredocquote: + Xcheck(ws, wp); + if (c) { + /* trailing \ is lost */ + *wp++ = CHAR; + *wp++ = '\\'; + *wp++ = CHAR; + *wp++ = c; + } + break; + } + break; + case '$': + subst_dollar: + c = getsc(); + if (c == '(') /*)*/ { + c = getsc(); + if (c == '(') /*)*/ { + PUSH_STATE(SASPAREN); + statep->ls_sasparen.nparen = 2; + statep->ls_sasparen.start = + Xsavepos(ws, wp); + *wp++ = EXPRSUB; + } else { + ungetsc(c); + PUSH_STATE(SCSPAREN); + statep->ls_scsparen.nparen = 1; + statep->ls_scsparen.csstate = 0; + *wp++ = COMSUB; + } + } else if (c == '{') /*}*/ { + *wp++ = OSUBST; + *wp++ = '{'; /*}*/ + wp = get_brace_var(&ws, wp); + c = getsc(); + /* allow :# and :% (ksh88 compat) */ + if (c == ':') { + *wp++ = CHAR; + *wp++ = c; + c = getsc(); + if (c == ':') { + *wp++ = CHAR; + *wp++ = '0'; + *wp++ = ADELIM; + *wp++ = ':'; + PUSH_STATE(SBRACE); + PUSH_STATE(SADELIM); + statep->ls_sadelim.style = SADELIM_BASH; + statep->ls_sadelim.delimiter = ':'; + statep->ls_sadelim.num = 1; + statep->ls_sadelim.nparen = 0; + break; + } else if (ksh_isdigit(c) || + c == '('/*)*/ || c == ' ' || + c == '$' /* XXX what else? */) { + /* substring subst. */ + if (c != ' ') { + *wp++ = CHAR; + *wp++ = ' '; + } + ungetsc(c); + PUSH_STATE(SBRACE); + PUSH_STATE(SADELIM); + statep->ls_sadelim.style = SADELIM_BASH; + statep->ls_sadelim.delimiter = ':'; + statep->ls_sadelim.num = 2; + statep->ls_sadelim.nparen = 0; + break; + } + } else if (c == '/') { + *wp++ = CHAR; + *wp++ = c; + if ((c = getsc()) == '/') { + *wp++ = ADELIM; + *wp++ = c; + } else + ungetsc(c); + PUSH_STATE(SBRACE); + PUSH_STATE(SADELIM); + statep->ls_sadelim.style = SADELIM_BASH; + statep->ls_sadelim.delimiter = '/'; + statep->ls_sadelim.num = 1; + statep->ls_sadelim.nparen = 0; + break; + } + /* If this is a trim operation, + * treat (,|,) specially in STBRACE. + */ + if (ctype(c, C_SUBOP2)) { + ungetsc(c); + PUSH_STATE(STBRACE); + } else { + ungetsc(c); + if (state == SDQUOTE) + PUSH_STATE(SQBRACE); + else + PUSH_STATE(SBRACE); + } + } else if (ksh_isalphx(c)) { + *wp++ = OSUBST; + *wp++ = 'X'; + do { + Xcheck(ws, wp); + *wp++ = c; + c = getsc(); + } while (ksh_isalnux(c)); + *wp++ = '\0'; + *wp++ = CSUBST; + *wp++ = 'X'; + ungetsc(c); + } else if (ctype(c, C_VAR1 | C_DIGIT)) { + Xcheck(ws, wp); + *wp++ = OSUBST; + *wp++ = 'X'; + *wp++ = c; + *wp++ = '\0'; + *wp++ = CSUBST; + *wp++ = 'X'; + } else if (c == '\'' && (state == SBASE)) { + /* XXX which other states are valid? */ + *wp++ = OQUOTE; + ignore_backslash_newline++; + PUSH_STATE(SEQUOTE); + statep->ls_sequote.got_NUL = false; + break; + } else { + *wp++ = CHAR; + *wp++ = '$'; + ungetsc(c); + } + break; + case '`': + subst_gravis: + PUSH_STATE(SBQUOTE); + *wp++ = COMSUB; + /* Need to know if we are inside double quotes + * since sh/AT&T-ksh translate the \" to " in + * "`...\"...`". + * This is not done in POSIX mode (section + * 3.2.3, Double Quotes: "The backquote shall + * retain its special meaning introducing the + * other form of command substitution (see + * 3.6.3). The portion of the quoted string + * from the initial backquote and the + * characters up to the next backquote that + * is not preceded by a backslash (having + * escape characters removed) defines that + * command whose output replaces `...` when + * the word is expanded." + * Section 3.6.3, Command Substitution: + * "Within the backquoted style of command + * substitution, backslash shall retain its + * literal meaning, except when followed by + * $ ` \."). + */ + statep->ls_sbquote.indquotes = 0; + s2 = statep; + base = state_info.base; + while (1) { + for (; s2 != base; s2--) { + if (s2->ls_state == SDQUOTE) { + statep->ls_sbquote.indquotes = 1; + break; + } + } + if (s2 != base) + break; + if (!(s2 = s2->ls_info.base)) + break; + base = s2-- - STATE_BSIZE; + } + break; + case QCHAR: + if (cf & LQCHAR) { + *wp++ = QCHAR; + *wp++ = getsc(); + break; + } + /* FALLTHROUGH */ + default: + store_char: + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SEQUOTE: + if (c == '\'') { + POP_STATE(); + *wp++ = CQUOTE; + ignore_backslash_newline--; + } else if (c == '\\') { + if ((c2 = unbksl(true, s_get, s_put)) == -1) + c2 = s_get(); + if (c2 == 0) + statep->ls_sequote.got_NUL = true; + if (!statep->ls_sequote.got_NUL) { + char ts[4]; + + if ((unsigned int)c2 < 0x100) { + *wp++ = QCHAR; + *wp++ = c2; + } else { + c = utf_wctomb(ts, c2 - 0x100); + ts[c] = 0; + for (c = 0; ts[c]; ++c) { + *wp++ = QCHAR; + *wp++ = ts[c]; + } + } + } + } else if (!statep->ls_sequote.got_NUL) { + *wp++ = QCHAR; + *wp++ = c; + } + break; + + case SSQUOTE: + if (c == '\'') { + POP_STATE(); + *wp++ = CQUOTE; + ignore_backslash_newline--; + } else { + *wp++ = QCHAR; + *wp++ = c; + } + break; + + case SDQUOTE: + if (c == '"') { + POP_STATE(); + *wp++ = CQUOTE; + } else + goto Subst; + break; + + case SCSPAREN: /* $( ... ) */ + /* todo: deal with $(...) quoting properly + * kludge to partly fake quoting inside $(...): doesn't + * really work because nested $(...) or ${...} inside + * double quotes aren't dealt with. + */ + switch (statep->ls_scsparen.csstate) { + case 0: /* normal */ + switch (c) { + case '(': + statep->ls_scsparen.nparen++; + break; + case ')': + statep->ls_scsparen.nparen--; + break; + case '\\': + statep->ls_scsparen.csstate = 1; + break; + case '"': + statep->ls_scsparen.csstate = 2; + break; + case '\'': + statep->ls_scsparen.csstate = 4; + ignore_backslash_newline++; + break; + } + break; + + case 1: /* backslash in normal mode */ + case 3: /* backslash in double quotes */ + --statep->ls_scsparen.csstate; + break; + + case 2: /* double quotes */ + if (c == '"') + statep->ls_scsparen.csstate = 0; + else if (c == '\\') + statep->ls_scsparen.csstate = 3; + break; + + case 4: /* single quotes */ + if (c == '\'') { + statep->ls_scsparen.csstate = 0; + ignore_backslash_newline--; + } + break; + } + if (statep->ls_scsparen.nparen == 0) { + POP_STATE(); + *wp++ = 0; /* end of COMSUB */ + } else + *wp++ = c; + break; + + case SASPAREN: /* $(( ... )) */ + /* XXX should nest using existing state machine + * (embed "...", $(...), etc.) */ + if (c == '(') + statep->ls_sasparen.nparen++; + else if (c == ')') { + statep->ls_sasparen.nparen--; + if (statep->ls_sasparen.nparen == 1) { + /*(*/ + if ((c2 = getsc()) == ')') { + POP_STATE(); + /* end of EXPRSUB */ + *wp++ = 0; + break; + } else { + char *s; + + ungetsc(c2); + /* mismatched parenthesis - + * assume we were really + * parsing a $(...) expression + */ + s = Xrestpos(ws, wp, + statep->ls_sasparen.start); + memmove(s + 1, s, wp - s); + *s++ = COMSUB; + *s = '('; /*)*/ + wp++; + statep->ls_scsparen.nparen = 1; + statep->ls_scsparen.csstate = 0; + state = statep->ls_state = + SCSPAREN; + } + } + } + *wp++ = c; + break; + + case SQBRACE: + if (c == '\\') { + /* + * perform POSIX "quote removal" if the back- + * slash is "special", i.e. same cases as the + * {case '\\':} in Subst: plus closing brace; + * in mksh code "quote removal" on '\c' means + * write QCHAR+c, otherwise CHAR+\+CHAR+c are + * emitted (in heredocquote:) + */ + if ((c = getsc()) == '"' || c == '\\' || + c == '$' || c == '`' || c == /*{*/'}') + goto store_qchar; + goto heredocquote; + } + goto common_SQBRACE; + + case SBRACE: + if (c == '\'') + goto open_ssquote; + else if (c == '\\') + goto getsc_qchar; + common_SQBRACE: + if (c == '"') + goto open_sdquote; + else if (c == '$') + goto subst_dollar; + else if (c == '`') + goto subst_gravis; + else if (c != /*{*/ '}') + goto store_char; + POP_STATE(); + *wp++ = CSUBST; + *wp++ = /*{*/ '}'; + break; + + case STBRACE: + /* Same as SBASE, except (,|,) treated specially */ + if (c == /*{*/ '}') { + POP_STATE(); + *wp++ = CSUBST; + *wp++ = /*{*/ '}'; + } else if (c == '|') { + *wp++ = SPAT; + } else if (c == '(') { + *wp++ = OPAT; + *wp++ = ' '; /* simile for @ */ + PUSH_STATE(SPATTERN); + } else + goto Sbase1; + break; + + case SBQUOTE: + if (c == '`') { + *wp++ = 0; + POP_STATE(); + } else if (c == '\\') { + switch (c = getsc()) { + case '\\': + case '$': case '`': + *wp++ = c; + break; + case '"': + if (statep->ls_sbquote.indquotes) { + *wp++ = c; + break; + } + /* FALLTHROUGH */ + default: + if (c) { + /* trailing \ is lost */ + *wp++ = '\\'; + *wp++ = c; + } + break; + } + } else + *wp++ = c; + break; + + case SWORD: /* ONEWORD */ + goto Subst; + + case SLETPAREN: /* LETEXPR: (( ... )) */ + /*(*/ + if (c == ')') { + if (statep->ls_sletparen.nparen > 0) + --statep->ls_sletparen.nparen; + else if ((c2 = getsc()) == /*(*/ ')') { + c = 0; + *wp++ = CQUOTE; + goto Done; + } else { + Source *s; + + ungetsc(c2); + /* mismatched parenthesis - + * assume we were really + * parsing a $(...) expression + */ + *wp = EOS; + sp = Xstring(ws, wp); + dp = wdstrip(sp, true, false); + s = pushs(SREREAD, source->areap); + s->start = s->str = s->u.freeme = dp; + s->next = source; + source = s; + return ('('/*)*/); + } + } else if (c == '(') + /* parenthesis inside quotes and backslashes + * are lost, but AT&T ksh doesn't count them + * either + */ + ++statep->ls_sletparen.nparen; + goto Sbase2; + +#ifndef MKSH_SMALL + case SLETARRAY: /* LETARRAY: =( ... ) */ + if (c == '('/*)*/) + ++statep->ls_sletarray.nparen; + else if (c == /*(*/')') + if (statep->ls_sletarray.nparen-- == 0) { + c = 0; + goto Done; + } + *wp++ = CHAR; + *wp++ = c; + break; +#endif + + case SHERESTRING: /* <<< delimiter */ + if (c == '\\') { + c = getsc(); + if (c) { + /* trailing \ is lost */ + *wp++ = QCHAR; + *wp++ = c; + } + /* invoke quoting mode */ + Xstring(ws, wp)[0] = QCHAR; + } else if (c == '$') { + if ((c2 = getsc()) == '\'') { + PUSH_STATE(SEQUOTE); + statep->ls_sequote.got_NUL = false; + goto sherestring_quoted; + } + ungetsc(c2); + goto sherestring_regular; + } else if (c == '\'') { + PUSH_STATE(SSQUOTE); + sherestring_quoted: + *wp++ = OQUOTE; + ignore_backslash_newline++; + /* invoke quoting mode */ + Xstring(ws, wp)[0] = QCHAR; + } else if (c == '"') { + state = statep->ls_state = SHEREDQUOTE; + *wp++ = OQUOTE; + /* just don't IFS split; no quoting mode */ + } else { + sherestring_regular: + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SHEREDELIM: /* <<,<<- delimiter */ + /* XXX chuck this state (and the next) - use + * the existing states ($ and \`...` should be + * stripped of their specialness after the + * fact). + */ + /* here delimiters need a special case since + * $ and `...` are not to be treated specially + */ + if (c == '\\') { + c = getsc(); + if (c) { + /* trailing \ is lost */ + *wp++ = QCHAR; + *wp++ = c; + } + } else if (c == '$') { + if ((c2 = getsc()) == '\'') { + PUSH_STATE(SEQUOTE); + statep->ls_sequote.got_NUL = false; + goto sheredelim_quoted; + } + ungetsc(c2); + goto sheredelim_regular; + } else if (c == '\'') { + PUSH_STATE(SSQUOTE); + sheredelim_quoted: + *wp++ = OQUOTE; + ignore_backslash_newline++; + } else if (c == '"') { + state = statep->ls_state = SHEREDQUOTE; + *wp++ = OQUOTE; + } else { + sheredelim_regular: + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SHEREDQUOTE: /* " in <<,<<- delimiter */ + if (c == '"') { + *wp++ = CQUOTE; + state = statep->ls_state = + /* dp[1] == '<' means here string */ + Xstring(ws, wp)[1] == '<' ? + SHERESTRING : SHEREDELIM; + } else { + if (c == '\\') { + switch (c = getsc()) { + case '\\': case '"': + case '$': case '`': + break; + default: + if (c) { + /* trailing \ lost */ + *wp++ = CHAR; + *wp++ = '\\'; + } + break; + } + } + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SPATTERN: /* in *(...|...) pattern (*+?@!) */ + if ( /*(*/ c == ')') { + *wp++ = CPAT; + POP_STATE(); + } else if (c == '|') { + *wp++ = SPAT; + } else if (c == '(') { + *wp++ = OPAT; + *wp++ = ' '; /* simile for @ */ + PUSH_STATE(SPATTERN); + } else + goto Sbase1; + break; + } + } + Done: + Xcheck(ws, wp); + if (statep != &states[1]) + /* XXX figure out what is missing */ + yyerror("no closing quote\n"); + +#ifndef MKSH_SMALL + if (state == SLETARRAY && statep->ls_sletarray.nparen != -1) + yyerror("%s: ')' missing\n", T_synerr); +#endif + + /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ + if (state == SHEREDELIM || state == SHERESTRING) + state = SBASE; + + dp = Xstring(ws, wp); + if ((c == '<' || c == '>' || c == '&') && state == SBASE) { + struct ioword *iop = alloc(sizeof(struct ioword), ATEMP); + + if (Xlength(ws, wp) == 0) + iop->unit = c == '<' ? 0 : 1; + else for (iop->unit = 0, c2 = 0; c2 < Xlength(ws, wp); c2 += 2) { + if (dp[c2] != CHAR) + goto no_iop; + if (!ksh_isdigit(dp[c2 + 1])) + goto no_iop; + iop->unit = (iop->unit * 10) + dp[c2 + 1] - '0'; + } + + if (iop->unit >= FDBASE) + goto no_iop; + + if (c == '&') { + if ((c2 = getsc()) != '>') { + ungetsc(c2); + goto no_iop; + } + c = c2; + iop->flag = IOBASH; + } else + iop->flag = 0; + + c2 = getsc(); + /* <<, >>, <> are ok, >< is not */ + if (c == c2 || (c == '<' && c2 == '>')) { + iop->flag |= c == c2 ? + (c == '>' ? IOCAT : IOHERE) : IORDWR; + if (iop->flag == IOHERE) { + if ((c2 = getsc()) == '-') + iop->flag |= IOSKIP; + else + ungetsc(c2); + } + } else if (c2 == '&') + iop->flag |= IODUP | (c == '<' ? IORDUP : 0); + else { + iop->flag |= c == '>' ? IOWRITE : IOREAD; + if (c == '>' && c2 == '|') + iop->flag |= IOCLOB; + else + ungetsc(c2); + } + + iop->name = NULL; + iop->delim = NULL; + iop->heredoc = NULL; + Xfree(ws, wp); /* free word */ + yylval.iop = iop; + return (REDIR); + no_iop: + ; + } + + if (wp == dp && state == SBASE) { + Xfree(ws, wp); /* free word */ + /* no word, process LEX1 character */ + if ((c == '|') || (c == '&') || (c == ';') || (c == '('/*)*/)) { + if ((c2 = getsc()) == c) + c = (c == ';') ? BREAK : + (c == '|') ? LOGOR : + (c == '&') ? LOGAND : + /* c == '(' ) */ MDPAREN; + else if (c == '|' && c2 == '&') + c = COPROC; + else + ungetsc(c2); + } else if (c == '\n') { + gethere(false); + if (cf & CONTIN) + goto Again; + } else if (c == '\0') + /* need here strings at EOF */ + gethere(true); + return (c); + } + + *wp++ = EOS; /* terminate word */ + yylval.cp = Xclose(ws, wp); + if (state == SWORD || state == SLETPAREN + /* XXX ONEWORD? */ +#ifndef MKSH_SMALL + || state == SLETARRAY +#endif + ) + return (LWORD); + + /* unget terminator */ + ungetsc(c); + + /* + * note: the alias-vs-function code below depends on several + * interna: starting from here, source->str is not modified; + * the way getsc() and ungetsc() operate; etc. + */ + + /* copy word to unprefixed string ident */ + sp = yylval.cp; + dp = ident; + if ((cf & HEREDELIM) && (sp[1] == '<')) + while (dp < ident+IDENT) { + if ((c = *sp++) == CHAR) + *dp++ = *sp++; + else if ((c != OQUOTE) && (c != CQUOTE)) + break; + } + else + while (dp < ident+IDENT && (c = *sp++) == CHAR) + *dp++ = *sp++; + /* Make sure the ident array stays '\0' padded */ + memset(dp, 0, (ident+IDENT) - dp + 1); + if (c != EOS) + *ident = '\0'; /* word is not unquoted */ + + if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) { + struct tbl *p; + uint32_t h = hash(ident); + + /* { */ + if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) && + (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) { + afree(yylval.cp, ATEMP); + return (p->val.i); + } + if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) && + (p->flag & ISSET)) { + /* + * this still points to the same character as the + * ungetsc'd terminator from above + */ + const char *cp = source->str; + + /* prefer POSIX but not Korn functions over aliases */ + while (*cp == ' ' || *cp == '\t') + /* + * this is like getsc() without skipping + * over Source boundaries (including not + * parsing ungetsc'd characters that got + * pushed into an SREREAD) which is what + * we want here anyway: find out whether + * the alias name is followed by a POSIX + * function definition (only the opening + * parenthesis is checked though) + */ + ++cp; + /* prefer functions over aliases */ + if (*cp == '(' /*)*/) + /* + * delete alias upon encountering function + * definition + */ + ktdelete(p); + else { + Source *s = source; + + while (s && (s->flags & SF_HASALIAS)) + if (s->u.tblp == p) + return (LWORD); + else + s = s->next; + /* push alias expansion */ + s = pushs(SALIAS, source->areap); + s->start = s->str = p->val.s; + s->u.tblp = p; + s->flags |= SF_HASALIAS; + s->next = source; + if (source->type == SEOF) { + /* prevent infinite recursion at EOS */ + source->u.tblp = p; + source->flags |= SF_HASALIAS; + } + source = s; + afree(yylval.cp, ATEMP); + goto Again; + } + } + } + + return (LWORD); +} + +static void +gethere(bool iseof) +{ + struct ioword **p; + + for (p = heres; p < herep; p++) + if (iseof && (*p)->delim[1] != '<') + /* only here strings at EOF */ + return; + else + readhere(*p); + herep = heres; +} + +/* + * read "<<word" text into temp file + */ + +static void +readhere(struct ioword *iop) +{ + int c; + char *volatile eof; + char *eofp; + int skiptabs; + XString xs; + char *xp; + int xpos; + + if (iop->delim[1] == '<') { + /* process the here string */ + xp = iop->heredoc = evalstr(iop->delim, DOBLANK); + c = strlen(xp) - 1; + memmove(xp, xp + 1, c); + xp[c] = '\n'; + return; + } + + eof = evalstr(iop->delim, 0); + + if (!(iop->flag & IOEVAL)) + ignore_backslash_newline++; + + Xinit(xs, xp, 256, ATEMP); + + for (;;) { + eofp = eof; + skiptabs = iop->flag & IOSKIP; + xpos = Xsavepos(xs, xp); + while ((c = getsc()) != 0) { + if (skiptabs) { + if (c == '\t') + continue; + skiptabs = 0; + } + if (c != *eofp) + break; + Xcheck(xs, xp); + Xput(xs, xp, c); + eofp++; + } + /* Allow EOF here so commands with out trailing newlines + * will work (eg, ksh -c '...', $(...), etc). + */ + if (*eofp == '\0' && (c == 0 || c == '\n')) { + xp = Xrestpos(xs, xp, xpos); + break; + } + ungetsc(c); + while ((c = getsc()) != '\n') { + if (c == 0) + yyerror("here document '%s' unclosed\n", eof); + Xcheck(xs, xp); + Xput(xs, xp, c); + } + Xcheck(xs, xp); + Xput(xs, xp, c); + } + Xput(xs, xp, '\0'); + iop->heredoc = Xclose(xs, xp); + + if (!(iop->flag & IOEVAL)) + ignore_backslash_newline--; +} + +void +yyerror(const char *fmt, ...) +{ + va_list va; + + /* pop aliases and re-reads */ + while (source->type == SALIAS || source->type == SREREAD) + source = source->next; + source->str = null; /* zap pending input */ + + error_prefix(true); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + errorfz(); +} + +/* + * input for yylex with alias expansion + */ + +Source * +pushs(int type, Area *areap) +{ + Source *s; + + s = alloc(sizeof(Source), areap); + memset(s, 0, sizeof(Source)); + s->type = type; + s->str = null; + s->areap = areap; + if (type == SFILE || type == SSTDIN) + XinitN(s->xs, 256, s->areap); + return (s); +} + +static int +getsc__(void) +{ + Source *s = source; + int c; + + getsc_again: + while ((c = *s->str++) == 0) { + s->str = NULL; /* return 0 for EOF by default */ + switch (s->type) { + case SEOF: + s->str = null; + return (0); + + case SSTDIN: + case SFILE: + getsc_line(s); + break; + + case SWSTR: + break; + + case SSTRING: + break; + + case SWORDS: + s->start = s->str = *s->u.strv++; + s->type = SWORDSEP; + break; + + case SWORDSEP: + if (*s->u.strv == NULL) { + s->start = s->str = "\n"; + s->type = SEOF; + } else { + s->start = s->str = " "; + s->type = SWORDS; + } + break; + + case SALIAS: + if (s->flags & SF_ALIASEND) { + /* pass on an unused SF_ALIAS flag */ + source = s->next; + source->flags |= s->flags & SF_ALIAS; + s = source; + } else if (*s->u.tblp->val.s && + (c = strnul(s->u.tblp->val.s)[-1], ksh_isspace(c))) { + source = s = s->next; /* pop source stack */ + /* Note that this alias ended with a space, + * enabling alias expansion on the following + * word. + */ + s->flags |= SF_ALIAS; + } else { + /* At this point, we need to keep the current + * alias in the source list so recursive + * aliases can be detected and we also need + * to return the next character. Do this + * by temporarily popping the alias to get + * the next character and then put it back + * in the source list with the SF_ALIASEND + * flag set. + */ + source = s->next; /* pop source stack */ + source->flags |= s->flags & SF_ALIAS; + c = getsc__(); + if (c) { + s->flags |= SF_ALIASEND; + s->ugbuf[0] = c; s->ugbuf[1] = '\0'; + s->start = s->str = s->ugbuf; + s->next = source; + source = s; + } else { + s = source; + /* avoid reading eof twice */ + s->str = NULL; + break; + } + } + continue; + + case SREREAD: + if (s->start != s->ugbuf) /* yuck */ + afree(s->u.freeme, ATEMP); + source = s = s->next; + continue; + } + if (s->str == NULL) { + s->type = SEOF; + s->start = s->str = null; + return ('\0'); + } + if (s->flags & SF_ECHO) { + shf_puts(s->str, shl_out); + shf_flush(shl_out); + } + } + /* check for UTF-8 byte order mark */ + if (s->flags & SF_FIRST) { + s->flags &= ~SF_FIRST; + if (((unsigned char)c == 0xEF) && + (((const unsigned char *)(s->str))[0] == 0xBB) && + (((const unsigned char *)(s->str))[1] == 0xBF)) { + s->str += 2; + UTFMODE = 1; + goto getsc_again; + } + } + return (c); +} + +static void +getsc_line(Source *s) +{ + char *xp = Xstring(s->xs, xp), *cp; + bool interactive = Flag(FTALKING) && s->type == SSTDIN; + int have_tty = interactive && (s->flags & SF_TTY); + + /* Done here to ensure nothing odd happens when a timeout occurs */ + XcheckN(s->xs, xp, LINE); + *xp = '\0'; + s->start = s->str = xp; + + if (have_tty && ksh_tmout) { + ksh_tmout_state = TMOUT_READING; + alarm(ksh_tmout); + } + if (interactive) + change_winsz(); + if (have_tty && ( +#if !MKSH_S_NOVI + Flag(FVI) || +#endif + Flag(FEMACS) || Flag(FGMACS))) { + int nread; + + nread = x_read(xp, LINE); + if (nread < 0) /* read error */ + nread = 0; + xp[nread] = '\0'; + xp += nread; + } else { + if (interactive) + pprompt(prompt, 0); + else + s->line++; + + while (1) { + char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf); + + if (!p && shf_error(s->u.shf) && + shf_errno(s->u.shf) == EINTR) { + shf_clearerr(s->u.shf); + if (trap) + runtraps(0); + continue; + } + if (!p || (xp = p, xp[-1] == '\n')) + break; + /* double buffer size */ + xp++; /* move past NUL so doubling works... */ + XcheckN(s->xs, xp, Xlength(s->xs, xp)); + xp--; /* ...and move back again */ + } + /* flush any unwanted input so other programs/builtins + * can read it. Not very optimal, but less error prone + * than flushing else where, dealing with redirections, + * etc. + * todo: reduce size of shf buffer (~128?) if SSTDIN + */ + if (s->type == SSTDIN) + shf_flush(s->u.shf); + } + /* XXX: temporary kludge to restore source after a + * trap may have been executed. + */ + source = s; + if (have_tty && ksh_tmout) { + ksh_tmout_state = TMOUT_EXECUTING; + alarm(0); + } + cp = Xstring(s->xs, xp); +#ifndef MKSH_SMALL + if (interactive && *cp == '!' && cur_prompt == PS1) { + int linelen; + + linelen = Xlength(s->xs, xp); + XcheckN(s->xs, xp, fc_e_n + /* NUL */ 1); + /* reload after potential realloc */ + cp = Xstring(s->xs, xp); + /* change initial '!' into space */ + *cp = ' '; + /* NUL terminate the current string */ + *xp = '\0'; + /* move the actual string forward */ + memmove(cp + fc_e_n, cp, linelen + /* NUL */ 1); + xp += fc_e_n; + /* prepend it with "fc -e -" */ + memcpy(cp, fc_e_, fc_e_n); + } +#endif + s->start = s->str = cp; + strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp)); + /* Note: if input is all nulls, this is not eof */ + if (Xlength(s->xs, xp) == 0) { + /* EOF */ + if (s->type == SFILE) + shf_fdclose(s->u.shf); + s->str = NULL; + } else if (interactive && *s->str && + (cur_prompt != PS1 || !ctype(*s->str, C_IFS | C_IFSWS))) { + histsave(&s->line, s->str, true, true); +#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY + } else if (interactive && cur_prompt == PS1) { + cp = Xstring(s->xs, xp); + while (*cp && ctype(*cp, C_IFSWS)) + ++cp; + if (!*cp) + histsync(); +#endif + } + if (interactive) + set_prompt(PS2, NULL); +} + +void +set_prompt(int to, Source *s) +{ + cur_prompt = to; + + switch (to) { + case PS1: /* command */ + /* Substitute ! and !! here, before substitutions are done + * so ! in expanded variables are not expanded. + * NOTE: this is not what AT&T ksh does (it does it after + * substitutions, POSIX doesn't say which is to be done. + */ + { + struct shf *shf; + char * volatile ps1; + Area *saved_atemp; + + ps1 = str_val(global("PS1")); + shf = shf_sopen(NULL, strlen(ps1) * 2, + SHF_WR | SHF_DYNAMIC, NULL); + while (*ps1) + if (*ps1 != '!' || *++ps1 == '!') + shf_putchar(*ps1++, shf); + else + shf_fprintf(shf, "%d", + s ? s->line + 1 : 0); + ps1 = shf_sclose(shf); + saved_atemp = ATEMP; + newenv(E_ERRH); + if (sigsetjmp(e->jbuf, 0)) { + prompt = safe_prompt; + /* Don't print an error - assume it has already + * been printed. Reason is we may have forked + * to run a command and the child may be + * unwinding its stack through this code as it + * exits. + */ + } else { + char *cp = substitute(ps1, 0); + strdupx(prompt, cp, saved_atemp); + } + quitenv(NULL); + } + break; + case PS2: /* command continuation */ + prompt = str_val(global("PS2")); + break; + } +} + +static int +dopprompt(const char *cp, int ntruncate, bool doprint) +{ + int columns = 0, lines = 0, indelimit = 0; + char delimiter = 0; + + /* Undocumented AT&T ksh feature: + * If the second char in the prompt string is \r then the first char + * is taken to be a non-printing delimiter and any chars between two + * instances of the delimiter are not considered to be part of the + * prompt length + */ + if (*cp && cp[1] == '\r') { + delimiter = *cp; + cp += 2; + } + for (; *cp; cp++) { + if (indelimit && *cp != delimiter) + ; + else if (*cp == '\n' || *cp == '\r') { + lines += columns / x_cols + ((*cp == '\n') ? 1 : 0); + columns = 0; + } else if (*cp == '\t') { + columns = (columns | 7) + 1; + } else if (*cp == '\b') { + if (columns > 0) + columns--; + } else if (*cp == delimiter) + indelimit = !indelimit; + else if (UTFMODE && ((unsigned char)*cp > 0x7F)) { + const char *cp2; + columns += utf_widthadj(cp, &cp2); + if (doprint && (indelimit || + (ntruncate < (x_cols * lines + columns)))) + shf_write(cp, cp2 - cp, shl_out); + cp = cp2 - /* loop increment */ 1; + continue; + } else + columns++; + if (doprint && (*cp != delimiter) && + (indelimit || (ntruncate < (x_cols * lines + columns)))) + shf_putc(*cp, shl_out); + } + if (doprint) + shf_flush(shl_out); + return (x_cols * lines + columns); +} + + +void +pprompt(const char *cp, int ntruncate) +{ + dopprompt(cp, ntruncate, true); +} + +int +promptlen(const char *cp) +{ + return (dopprompt(cp, 0, false)); +} + +/* Read the variable part of a ${...} expression (ie, up to but not including + * the :[-+?=#%] or close-brace. + */ +static char * +get_brace_var(XString *wsp, char *wp) +{ + enum parse_state { + PS_INITIAL, PS_SAW_HASH, PS_IDENT, + PS_NUMBER, PS_VAR1 + } state; + char c; + + state = PS_INITIAL; + while (1) { + c = getsc(); + /* State machine to figure out where the variable part ends. */ + switch (state) { + case PS_INITIAL: + if (c == '#' || c == '!' || c == '%') { + state = PS_SAW_HASH; + break; + } + /* FALLTHROUGH */ + case PS_SAW_HASH: + if (ksh_isalphx(c)) + state = PS_IDENT; + else if (ksh_isdigit(c)) + state = PS_NUMBER; + else if (ctype(c, C_VAR1)) + state = PS_VAR1; + else + goto out; + break; + case PS_IDENT: + if (!ksh_isalnux(c)) { + if (c == '[') { + char *tmp, *p; + + if (!arraysub(&tmp)) + yyerror("missing ]\n"); + *wp++ = c; + for (p = tmp; *p; ) { + Xcheck(*wsp, wp); + *wp++ = *p++; + } + afree(tmp, ATEMP); + c = getsc(); /* the ] */ + } + goto out; + } + break; + case PS_NUMBER: + if (!ksh_isdigit(c)) + goto out; + break; + case PS_VAR1: + goto out; + } + Xcheck(*wsp, wp); + *wp++ = c; + } + out: + *wp++ = '\0'; /* end of variable part */ + ungetsc(c); + return (wp); +} + +/* + * Save an array subscript - returns true if matching bracket found, false + * if eof or newline was found. + * (Returned string double null terminated) + */ +static int +arraysub(char **strp) +{ + XString ws; + char *wp; + char c; + int depth = 1; /* we are just past the initial [ */ + + Xinit(ws, wp, 32, ATEMP); + + do { + c = getsc(); + Xcheck(ws, wp); + *wp++ = c; + if (c == '[') + depth++; + else if (c == ']') + depth--; + } while (depth > 0 && c && c != '\n'); + + *wp++ = '\0'; + *strp = Xclose(ws, wp); + + return (depth == 0 ? 1 : 0); +} + +/* Unget a char: handles case when we are already at the start of the buffer */ +static const char * +ungetsc(int c) +{ + if (backslash_skip) + backslash_skip--; + /* Don't unget eof... */ + if (source->str == null && c == '\0') + return (source->str); + if (source->str > source->start) + source->str--; + else { + Source *s; + + s = pushs(SREREAD, source->areap); + s->ugbuf[0] = c; s->ugbuf[1] = '\0'; + s->start = s->str = s->ugbuf; + s->next = source; + source = s; + } + return (source->str); +} + + +/* Called to get a char that isn't a \newline sequence. */ +static int +getsc_bn(void) +{ + int c, c2; + + if (ignore_backslash_newline) + return (getsc_()); + + if (backslash_skip == 1) { + backslash_skip = 2; + return (getsc_()); + } + + backslash_skip = 0; + + while (1) { + c = getsc_(); + if (c == '\\') { + if ((c2 = getsc_()) == '\n') + /* ignore the \newline; get the next char... */ + continue; + ungetsc(c2); + backslash_skip = 1; + } + return (c); + } +} + +static Lex_state * +push_state_(State_info *si, Lex_state *old_end) +{ + Lex_state *news = alloc(STATE_BSIZE * sizeof(Lex_state), ATEMP); + + news[0].ls_info.base = old_end; + si->base = &news[0]; + si->end = &news[STATE_BSIZE]; + return (&news[1]); +} + +static Lex_state * +pop_state_(State_info *si, Lex_state *old_end) +{ + Lex_state *old_base = si->base; + + si->base = old_end->ls_info.base - STATE_BSIZE; + si->end = old_end->ls_info.base; + + afree(old_base, ATEMP); + + return (si->base + STATE_BSIZE - 1); +} + +static int +s_get(void) +{ + return (getsc()); +} + +static void +s_put(int c) +{ + ungetsc(c); +} diff --git a/mksh/src/main.c b/mksh/src/main.c new file mode 100644 index 0000000..f962dd4 --- /dev/null +++ b/mksh/src/main.c @@ -0,0 +1,1479 @@ +/* $OpenBSD: main.c,v 1.46 2010/05/19 17:36:08 jasper Exp $ */ +/* $OpenBSD: tty.c,v 1.9 2006/03/14 22:08:01 deraadt Exp $ */ +/* $OpenBSD: io.c,v 1.22 2006/03/17 16:30:13 millert Exp $ */ +/* $OpenBSD: table.c,v 1.13 2009/01/17 22:06:44 millert Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#define EXTERN +#include "sh.h" + +#if HAVE_LANGINFO_CODESET +#include <langinfo.h> +#endif +#if HAVE_SETLOCALE_CTYPE +#include <locale.h> +#endif + +__RCSID("$MirOS: src/bin/mksh/main.c,v 1.167 2010/07/04 17:45:15 tg Exp $"); + +extern char **environ; + +#if !HAVE_SETRESUGID +extern uid_t kshuid; +extern gid_t kshgid, kshegid; +#endif + +#ifndef MKSHRC_PATH +#define MKSHRC_PATH "~/.mkshrc" +#endif + +#ifndef MKSH_DEFAULT_TMPDIR +#define MKSH_DEFAULT_TMPDIR "/tmp" +#endif + +static void reclaim(void); +static void remove_temps(struct temp *); +void chvt_reinit(void); +Source *mksh_init(int, const char *[]); +#ifdef SIGWINCH +static void x_sigwinch(int); +#endif + +static const char initifs[] = "IFS= \t\n"; + +static const char initsubs[] = + "${PS2=> } ${PS3=#? } ${PS4=+ } ${SECONDS=0} ${TMOUT=0}"; + +static const char *initcoms[] = { + T_typeset, "-r", initvsn, NULL, + T_typeset, "-x", "HOME", "PATH", "RANDOM", "SHELL", NULL, + T_typeset, "-i10", "COLUMNS", "LINES", "OPTIND", "PGRP", "PPID", + "RANDOM", "SECONDS", "TMOUT", "USER_ID", NULL, + "alias", + "integer=typeset -i", + T_local_typeset, + "hash=alias -t", /* not "alias -t --": hash -r needs to work */ + "type=whence -v", +#ifndef MKSH_UNEMPLOYED + "suspend=kill -STOP $$", +#endif + "autoload=typeset -fu", + "functions=typeset -f", + "history=fc -l", + "nameref=typeset -n", + "nohup=nohup ", + r_fc_e_, + "source=PATH=$PATH:. command .", + "login=exec login", + NULL, + /* this is what AT&T ksh seems to track, with the addition of emacs */ + "alias", "-tU", + "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls", + "make", "mv", "pr", "rm", "sed", "sh", "vi", "who", NULL, + NULL +}; + +static int initio_done; + +struct env *e = &kshstate_v.env_; + +void +chvt_reinit(void) +{ + kshpid = procpid = getpid(); + ksheuid = geteuid(); + kshpgrp = getpgrp(); + kshppid = getppid(); +} + +Source * +mksh_init(int argc, const char *argv[]) +{ + int argi, i; + Source *s; + struct block *l; + unsigned char restricted, errexit, utf_flag; + const char **wp; + struct tbl *vp; + struct stat s_stdin; +#if !defined(_PATH_DEFPATH) && defined(_CS_PATH) + size_t k; + char *cp; +#endif + + /* do things like getpgrp() et al. */ + chvt_reinit(); + + /* make sure argv[] is sane */ + if (!*argv) { + static const char *empty_argv[] = { + "mksh", NULL + }; + + argv = empty_argv; + argc = 1; + } + kshname = *argv; + + ainit(&aperm); /* initialise permanent Area */ + + /* set up base environment */ + kshstate_v.env_.type = E_NONE; + ainit(&kshstate_v.env_.area); + newblock(); /* set up global l->vars and l->funs */ + + /* Do this first so output routines (eg, errorf, shellf) can work */ + initio(); + + argi = parse_args(argv, OF_FIRSTTIME, NULL); + if (argi < 0) + return (NULL); + + initvar(); + + initctypes(); + + inittraps(); + + coproc_init(); + + /* set up variable and command dictionaries */ + ktinit(&taliases, APERM, 0); + ktinit(&aliases, APERM, 0); +#ifndef MKSH_NOPWNAM + ktinit(&homedirs, APERM, 0); +#endif + + /* define shell keywords */ + initkeywords(); + + /* define built-in commands */ + ktinit(&builtins, APERM, + /* must be 80% of 2^n (currently 44 builtins) */ 64); + for (i = 0; mkshbuiltins[i].name != NULL; i++) + builtin(mkshbuiltins[i].name, mkshbuiltins[i].func); + + init_histvec(); + +#ifdef _PATH_DEFPATH + def_path = _PATH_DEFPATH; +#else +#ifdef _CS_PATH + if ((k = confstr(_CS_PATH, NULL, 0)) != (size_t)-1 && k > 0 && + confstr(_CS_PATH, cp = alloc(k + 1, APERM), k + 1) == k + 1) + def_path = cp; + else +#endif + /* + * this is uniform across all OSes unless it + * breaks somewhere; don't try to optimise, + * e.g. add stuff for Interix or remove /usr + * for HURD, because e.g. Debian GNU/HURD is + * "keeping a regular /usr"; this is supposed + * to be a sane 'basic' default PATH + */ + def_path = "/bin:/usr/bin:/sbin:/usr/sbin"; +#endif + + /* Set PATH to def_path (will set the path global variable). + * (import of environment below will probably change this setting). + */ + vp = global("PATH"); + /* setstr can't fail here */ + setstr(vp, def_path, KSH_RETURN_ERROR); + + /* Turn on nohup by default for now - will change to off + * by default once people are aware of its existence + * (AT&T ksh does not have a nohup option - it always sends + * the hup). + */ + Flag(FNOHUP) = 1; + + /* Turn on brace expansion by default. AT&T kshs that have + * alternation always have it on. + */ + Flag(FBRACEEXPAND) = 1; + + /* Set edit mode to emacs by default, may be overridden + * by the environment or the user. Also, we want tab completion + * on in vi by default. */ + change_flag(FEMACS, OF_SPECIAL, 1); +#if !MKSH_S_NOVI + Flag(FVITABCOMPLETE) = 1; +#endif + +#ifdef MKSH_BINSHREDUCED + /* set FSH if we're called as -sh or /bin/sh or so */ + { + const char *cc; + + cc = kshname; + i = 0; argi = 0; + while (cc[i] != '\0') + /* the following line matches '-' and '/' ;-) */ + if ((cc[i++] | 2) == '/') + argi = i; + if (((cc[argi] | 0x20) == 's') && ((cc[argi + 1] | 0x20) == 'h')) + change_flag(FSH, OF_FIRSTTIME, 1); + } +#endif + + /* import environment */ + if (environ != NULL) + for (wp = (const char **)environ; *wp != NULL; wp++) + typeset(*wp, IMPORT | EXPORT, 0, 0, 0); + + typeset(initifs, 0, 0, 0, 0); /* for security */ + + /* assign default shell variable values */ + substitute(initsubs, 0); + + /* Figure out the current working directory and set $PWD */ + { + struct stat s_pwd, s_dot; + struct tbl *pwd_v = global("PWD"); + char *pwd = str_val(pwd_v); + char *pwdx = pwd; + + /* Try to use existing $PWD if it is valid */ + if (pwd[0] != '/' || + stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 || + s_pwd.st_dev != s_dot.st_dev || + s_pwd.st_ino != s_dot.st_ino) + pwdx = NULL; + set_current_wd(pwdx); + if (current_wd[0]) + simplify_path(current_wd); + /* Only set pwd if we know where we are or if it had a + * bogus value + */ + if (current_wd[0] || pwd != null) + /* setstr can't fail here */ + setstr(pwd_v, current_wd, KSH_RETURN_ERROR); + } + + for (wp = initcoms; *wp != NULL; wp++) { + shcomexec(wp); + while (*wp != NULL) + wp++; + } + setint(global("COLUMNS"), 0); + setint(global("LINES"), 0); + setint(global("OPTIND"), 1); + + safe_prompt = ksheuid ? "$ " : "# "; + vp = global("PS1"); + /* Set PS1 if unset or we are root and prompt doesn't contain a # */ + if (!(vp->flag & ISSET) || + (!ksheuid && !strchr(str_val(vp), '#'))) + /* setstr can't fail here */ + setstr(vp, safe_prompt, KSH_RETURN_ERROR); + setint((vp = global("PGRP")), (mksh_uari_t)kshpgrp); + vp->flag |= INT_U; + setint((vp = global("PPID")), (mksh_uari_t)kshppid); + vp->flag |= INT_U; + setint((vp = global("RANDOM")), (mksh_uari_t)evilhash(kshname)); + vp->flag |= INT_U; + setint((vp = global("USER_ID")), (mksh_uari_t)ksheuid); + vp->flag |= INT_U; + + /* Set this before parsing arguments */ +#if HAVE_SETRESUGID + Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid(); +#else + Flag(FPRIVILEGED) = (kshuid = getuid()) != ksheuid || + (kshgid = getgid()) != (kshegid = getegid()); +#endif + + /* this to note if monitor is set on command line (see below) */ +#ifndef MKSH_UNEMPLOYED + Flag(FMONITOR) = 127; +#endif + /* this to note if utf-8 mode is set on command line (see below) */ + UTFMODE = 2; + + argi = parse_args(argv, OF_CMDLINE, NULL); + if (argi < 0) + return (NULL); + + /* process this later only, default to off (hysterical raisins) */ + utf_flag = UTFMODE; + UTFMODE = 0; + + if (Flag(FCOMMAND)) { + s = pushs(SSTRING, ATEMP); + if (!(s->start = s->str = argv[argi++])) + errorf("-c requires an argument"); +#ifdef MKSH_MIDNIGHTBSD01ASH_COMPAT + /* compatibility to MidnightBSD 0.1 /bin/sh (kludge) */ + if (Flag(FSH) && argv[argi] && !strcmp(argv[argi], "--")) + ++argi; +#endif + if (argv[argi]) + kshname = argv[argi++]; + } else if (argi < argc && !Flag(FSTDIN)) { + s = pushs(SFILE, ATEMP); + s->file = argv[argi++]; + s->u.shf = shf_open(s->file, O_RDONLY, 0, + SHF_MAPHI | SHF_CLEXEC); + if (s->u.shf == NULL) { + shl_stdout_ok = 0; + warningf(true, "%s: %s", s->file, strerror(errno)); + /* mandated by SUSv4 */ + exstat = 127; + unwind(LERROR); + } + kshname = s->file; + } else { + Flag(FSTDIN) = 1; + s = pushs(SSTDIN, ATEMP); + s->file = "<stdin>"; + s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0), + NULL); + if (isatty(0) && isatty(2)) { + Flag(FTALKING) = Flag(FTALKING_I) = 1; + /* The following only if isatty(0) */ + s->flags |= SF_TTY; + s->u.shf->flags |= SHF_INTERRUPT; + s->file = NULL; + } + } + + /* this bizarreness is mandated by POSIX */ + if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) && + Flag(FTALKING)) + reset_nonblock(0); + + /* initialise job control */ + j_init(); + /* set: 0/1; unset: 2->0 */ + UTFMODE = utf_flag & 1; + /* Do this after j_init(), as tty_fd is not initialised until then */ + if (Flag(FTALKING)) { + if (utf_flag == 2) { +#ifndef MKSH_ASSUME_UTF8 +#define isuc(x) (((x) != NULL) && \ + (stristr((x), "UTF-8") || stristr((x), "utf8"))) + /* Check if we're in a UTF-8 locale */ + const char *ccp; + +#if HAVE_SETLOCALE_CTYPE + ccp = setlocale(LC_CTYPE, ""); +#if HAVE_LANGINFO_CODESET + if (!isuc(ccp)) + ccp = nl_langinfo(CODESET); +#endif +#else + /* these were imported from environ earlier */ + ccp = str_val(global("LC_ALL")); + if (ccp == null) + ccp = str_val(global("LC_CTYPE")); + if (ccp == null) + ccp = str_val(global("LANG")); +#endif + UTFMODE = isuc(ccp); +#undef isuc +#elif MKSH_ASSUME_UTF8 + UTFMODE = 1; +#else + UTFMODE = 0; +#endif + } + x_init(); + } + +#ifdef SIGWINCH + sigtraps[SIGWINCH].flags |= TF_SHELL_USES; + setsig(&sigtraps[SIGWINCH], x_sigwinch, + SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); +#endif + + l = e->loc; + l->argv = &argv[argi - 1]; + l->argc = argc - argi; + l->argv[0] = kshname; + getopts_reset(1); + + /* Disable during .profile/ENV reading */ + restricted = Flag(FRESTRICTED); + Flag(FRESTRICTED) = 0; + errexit = Flag(FERREXIT); + Flag(FERREXIT) = 0; + + /* Do this before profile/$ENV so that if it causes problems in them, + * user will know why things broke. + */ + if (!current_wd[0] && Flag(FTALKING)) + warningf(false, "Cannot determine current working directory"); + + if (Flag(FLOGIN)) { + include(KSH_SYSTEM_PROFILE, 0, NULL, 1); + if (!Flag(FPRIVILEGED)) + include(substitute("$HOME/.profile", 0), 0, + NULL, 1); + } + if (Flag(FPRIVILEGED)) + include("/etc/suid_profile", 0, NULL, 1); + else if (Flag(FTALKING)) { + char *env_file; + + /* include $ENV */ + env_file = substitute(substitute("${ENV:-" MKSHRC_PATH "}", 0), + DOTILDE); + if (*env_file != '\0') + include(env_file, 0, NULL, 1); + } + + if (restricted) { + static const char *restr_com[] = { + T_typeset, "-r", "PATH", + "ENV", "SHELL", + NULL + }; + shcomexec(restr_com); + /* After typeset command... */ + Flag(FRESTRICTED) = 1; + } + Flag(FERREXIT) = errexit; + + if (Flag(FTALKING)) { + hist_init(s); + alarm_init(); + } else + Flag(FTRACKALL) = 1; /* set after ENV */ + + return (s); +} + +int +main(int argc, const char *argv[]) +{ + Source *s; + + kshstate_v.lcg_state_ = 5381; + + if ((s = mksh_init(argc, argv))) { + /* put more entropy into the LCG */ + change_random(s, sizeof(*s)); + /* doesn’t return */ + shell(s, true); + } + return (1); +} + +int +include(const char *name, int argc, const char **argv, int intr_ok) +{ + Source *volatile s = NULL; + struct shf *shf; + const char **volatile old_argv; + volatile int old_argc; + int i; + + shf = shf_open(name, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); + if (shf == NULL) + return (-1); + + if (argv) { + old_argv = e->loc->argv; + old_argc = e->loc->argc; + } else { + old_argv = NULL; + old_argc = 0; + } + newenv(E_INCL); + i = sigsetjmp(e->jbuf, 0); + if (i) { + quitenv(s ? s->u.shf : NULL); + if (old_argv) { + e->loc->argv = old_argv; + e->loc->argc = old_argc; + } + switch (i) { + case LRETURN: + case LERROR: + return (exstat & 0xff); /* see below */ + case LINTR: + /* intr_ok is set if we are including .profile or $ENV. + * If user ^Cs out, we don't want to kill the shell... + */ + if (intr_ok && (exstat - 128) != SIGTERM) + return (1); + /* FALLTHROUGH */ + case LEXIT: + case LLEAVE: + case LSHELL: + unwind(i); + /* NOTREACHED */ + default: + internal_errorf("include: %d", i); + /* NOTREACHED */ + } + } + if (argv) { + e->loc->argv = argv; + e->loc->argc = argc; + } + s = pushs(SFILE, ATEMP); + s->u.shf = shf; + strdupx(s->file, name, ATEMP); + i = shell(s, false); + quitenv(s->u.shf); + if (old_argv) { + e->loc->argv = old_argv; + e->loc->argc = old_argc; + } + return (i & 0xff); /* & 0xff to ensure value not -1 */ +} + +/* spawn a command into a shell optionally keeping track of the line number */ +int +command(const char *comm, int line) +{ + Source *s; + + s = pushs(SSTRING, ATEMP); + s->start = s->str = comm; + s->line = line; + return (shell(s, false)); +} + +/* + * run the commands from the input source, returning status. + */ +int +shell(Source * volatile s, volatile int toplevel) +{ + struct op *t; + volatile int wastty = s->flags & SF_TTY; + volatile int attempts = 13; + volatile int interactive = Flag(FTALKING) && toplevel; + Source *volatile old_source = source; + int i; + + s->flags |= SF_FIRST; /* enable UTF-8 BOM check */ + + newenv(E_PARSE); + if (interactive) + really_exit = 0; + i = sigsetjmp(e->jbuf, 0); + if (i) { + switch (i) { + case LINTR: /* we get here if SIGINT not caught or ignored */ + case LERROR: + case LSHELL: + if (interactive) { + if (i == LINTR) + shellf("\n"); + /* Reset any eof that was read as part of a + * multiline command. + */ + if (Flag(FIGNOREEOF) && s->type == SEOF && + wastty) + s->type = SSTDIN; + /* Used by exit command to get back to + * top level shell. Kind of strange since + * interactive is set if we are reading from + * a tty, but to have stopped jobs, one only + * needs FMONITOR set (not FTALKING/SF_TTY)... + */ + /* toss any input we have so far */ + s->start = s->str = null; + break; + } + /* FALLTHROUGH */ + case LEXIT: + case LLEAVE: + case LRETURN: + source = old_source; + quitenv(NULL); + unwind(i); /* keep on going */ + /* NOTREACHED */ + default: + source = old_source; + quitenv(NULL); + internal_errorf("shell: %d", i); + /* NOTREACHED */ + } + } + while (1) { + if (trap) + runtraps(0); + + if (s->next == NULL) { + if (Flag(FVERBOSE)) + s->flags |= SF_ECHO; + else + s->flags &= ~SF_ECHO; + } + if (interactive) { + j_notify(); + set_prompt(PS1, s); + } + t = compile(s); + if (t != NULL && t->type == TEOF) { + if (wastty && Flag(FIGNOREEOF) && --attempts > 0) { + shellf("Use 'exit' to leave ksh\n"); + s->type = SSTDIN; + } else if (wastty && !really_exit && + j_stopped_running()) { + really_exit = 1; + s->type = SSTDIN; + } else { + /* this for POSIX which says EXIT traps + * shall be taken in the environment + * immediately after the last command + * executed. + */ + if (toplevel) + unwind(LEXIT); + break; + } + } + if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) + exstat = execute(t, 0, NULL); + + if (t != NULL && t->type != TEOF && interactive && really_exit) + really_exit = 0; + + reclaim(); + } + quitenv(NULL); + source = old_source; + return (exstat); +} + +/* return to closest error handler or shell(), exit if none found */ +void +unwind(int i) +{ + /* ordering for EXIT vs ERR is a bit odd (this is what AT&T ksh does) */ + if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) && + sigtraps[SIGEXIT_].trap)) { + runtrap(&sigtraps[SIGEXIT_]); + i = LLEAVE; + } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) { + runtrap(&sigtraps[SIGERR_]); + i = LLEAVE; + } + while (1) { + switch (e->type) { + case E_PARSE: + case E_FUNC: + case E_INCL: + case E_LOOP: + case E_ERRH: + siglongjmp(e->jbuf, i); + /* NOTREACHED */ + case E_NONE: + if (i == LINTR) + e->flags |= EF_FAKE_SIGDIE; + /* FALLTHROUGH */ + default: + quitenv(NULL); + } + } +} + +void +newenv(int type) +{ + struct env *ep; + char *cp; + + /* + * struct env includes ALLOC_ITEM for alignment constraints + * so first get the actually used memory, then assign it + */ + cp = alloc(sizeof(struct env) - ALLOC_SIZE, ATEMP); + ep = (void *)(cp - ALLOC_SIZE); /* undo what alloc() did */ + /* initialise public members of struct env (not the ALLOC_ITEM) */ + ainit(&ep->area); + ep->oenv = e; + ep->loc = e->loc; + ep->savefd = NULL; + ep->temps = NULL; + ep->type = type; + ep->flags = 0; + /* jump buffer is invalid because flags == 0 */ + e = ep; +} + +void +quitenv(struct shf *shf) +{ + struct env *ep = e; + char *cp; + int fd; + + if (ep->oenv && ep->oenv->loc != ep->loc) + popblock(); + if (ep->savefd != NULL) { + for (fd = 0; fd < NUFILE; fd++) + /* if ep->savefd[fd] < 0, means fd was closed */ + if (ep->savefd[fd]) + restfd(fd, ep->savefd[fd]); + if (ep->savefd[2]) /* Clear any write errors */ + shf_reopen(2, SHF_WR, shl_out); + } + /* Bottom of the stack. + * Either main shell is exiting or cleanup_parents_env() was called. + */ + if (ep->oenv == NULL) { + if (ep->type == E_NONE) { /* Main shell exiting? */ +#if HAVE_PERSISTENT_HISTORY + if (Flag(FTALKING)) + hist_finish(); +#endif + j_exit(); + if (ep->flags & EF_FAKE_SIGDIE) { + int sig = exstat - 128; + + /* ham up our death a bit (AT&T ksh + * only seems to do this for SIGTERM) + * Don't do it for SIGQUIT, since we'd + * dump a core.. + */ + if ((sig == SIGINT || sig == SIGTERM) && + (kshpgrp == kshpid)) { + setsig(&sigtraps[sig], SIG_DFL, + SS_RESTORE_CURR | SS_FORCE); + kill(0, sig); + } + } + } + if (shf) + shf_close(shf); + reclaim(); + exit(exstat); + } + if (shf) + shf_close(shf); + reclaim(); + + e = e->oenv; + + /* free the struct env - tricky due to the ALLOC_ITEM inside */ + cp = (void *)ep; + afree(cp + ALLOC_SIZE, ATEMP); +} + +/* Called after a fork to cleanup stuff left over from parents environment */ +void +cleanup_parents_env(void) +{ + struct env *ep; + int fd; + + mkssert(e != NULL); + + /* + * Don't clean up temporary files - parent will probably need them. + * Also, can't easily reclaim memory since variables, etc. could be + * anywhere. + */ + + /* close all file descriptors hiding in savefd */ + for (ep = e; ep; ep = ep->oenv) { + if (ep->savefd) { + for (fd = 0; fd < NUFILE; fd++) + if (ep->savefd[fd] > 0) + close(ep->savefd[fd]); + afree(ep->savefd, &ep->area); + ep->savefd = NULL; + } + } + e->oenv = NULL; +} + +/* Called just before an execve cleanup stuff temporary files */ +void +cleanup_proc_env(void) +{ + struct env *ep; + + for (ep = e; ep; ep = ep->oenv) + remove_temps(ep->temps); +} + +/* remove temp files and free ATEMP Area */ +static void +reclaim(void) +{ + remove_temps(e->temps); + e->temps = NULL; + afreeall(&e->area); +} + +static void +remove_temps(struct temp *tp) +{ + for (; tp != NULL; tp = tp->next) + if (tp->pid == procpid) + unlink(tp->name); +} + +/* Initialise tty_fd. Used for saving/reseting tty modes upon + * foreground job completion and for setting up tty process group. + */ +void +tty_init(bool init_ttystate, bool need_tty) +{ + bool do_close = true; + int tfd; + + if (tty_fd >= 0) { + close(tty_fd); + tty_fd = -1; + } + tty_devtty = 1; + +#ifdef _UWIN + /* XXX imake style */ + if (isatty(3)) + tfd = 3; + else +#endif + if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { + tty_devtty = 0; + if (need_tty) + warningf(false, + "No controlling tty (open /dev/tty: %s)", + strerror(errno)); + } + if (tfd < 0) { + do_close = false; + if (isatty(0)) + tfd = 0; + else if (isatty(2)) + tfd = 2; + else { + if (need_tty) + warningf(false, + "Can't find tty file descriptor"); + return; + } + } + if ((tty_fd = fcntl(tfd, F_DUPFD, FDBASE)) < 0) { + if (need_tty) + warningf(false, "j_ttyinit: dup of tty fd failed: %s", + strerror(errno)); + } else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) { + if (need_tty) + warningf(false, + "j_ttyinit: can't set close-on-exec flag: %s", + strerror(errno)); + close(tty_fd); + tty_fd = -1; + } else if (init_ttystate) + tcgetattr(tty_fd, &tty_state); + if (do_close) + close(tfd); +} + +void +tty_close(void) +{ + if (tty_fd >= 0) { + close(tty_fd); + tty_fd = -1; + } +} + +/* A shell error occurred (eg, syntax error, etc.) */ +void +errorf(const char *fmt, ...) +{ + va_list va; + + shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + exstat = 1; + if (*fmt != 1) { + error_prefix(true); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + } + shf_flush(shl_out); + unwind(LERROR); +} + +/* like errorf(), but no unwind is done */ +void +warningf(bool fileline, const char *fmt, ...) +{ + va_list va; + + error_prefix(fileline); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + shf_flush(shl_out); +} + +/* Used by built-in utilities to prefix shell and utility name to message + * (also unwinds environments for special builtins). + */ +void +bi_errorf(const char *fmt, ...) +{ + va_list va; + + shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + exstat = 1; + if (*fmt != 1) { + error_prefix(true); + /* not set when main() calls parse_args() */ + if (builtin_argv0) + shf_fprintf(shl_out, "%s: ", builtin_argv0); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + } + shf_flush(shl_out); + /* POSIX special builtins and ksh special builtins cause + * non-interactive shells to exit. + * XXX odd use of KEEPASN; also may not want LERROR here + */ + if (builtin_flag & SPEC_BI) { + builtin_argv0 = NULL; + unwind(LERROR); + } +} + +/* Called when something that shouldn't happen does */ +void +internal_verrorf(const char *fmt, va_list ap) +{ + shf_fprintf(shl_out, "internal error: "); + shf_vfprintf(shl_out, fmt, ap); + shf_putchar('\n', shl_out); + shf_flush(shl_out); +} + +void +internal_errorf(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + internal_verrorf(fmt, va); + va_end(va); + unwind(LERROR); +} + +void +internal_warningf(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + internal_verrorf(fmt, va); + va_end(va); +} + +/* used by error reporting functions to print "ksh: .kshrc[25]: " */ +void +error_prefix(bool fileline) +{ + /* Avoid foo: foo[2]: ... */ + if (!fileline || !source || !source->file || + strcmp(source->file, kshname) != 0) + shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-')); + if (fileline && source && source->file != NULL) { + shf_fprintf(shl_out, "%s[%d]: ", source->file, + source->errline > 0 ? source->errline : source->line); + source->errline = 0; + } +} + +/* printf to shl_out (stderr) with flush */ +void +shellf(const char *fmt, ...) +{ + va_list va; + + if (!initio_done) /* shl_out may not be set up yet... */ + return; + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_flush(shl_out); +} + +/* printf to shl_stdout (stdout) */ +void +shprintf(const char *fmt, ...) +{ + va_list va; + + if (!shl_stdout_ok) + internal_errorf("shl_stdout not valid"); + va_start(va, fmt); + shf_vfprintf(shl_stdout, fmt, va); + va_end(va); +} + +/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */ +int +can_seek(int fd) +{ + struct stat statb; + + return (fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ? + SHF_UNBUF : 0); +} + +struct shf shf_iob[3]; + +void +initio(void) +{ + shf_fdopen(1, SHF_WR, shl_stdout); /* force buffer allocation */ + shf_fdopen(2, SHF_WR, shl_out); + shf_fdopen(2, SHF_WR, shl_spare); /* force buffer allocation */ + initio_done = 1; +} + +/* A dup2() with error checking */ +int +ksh_dup2(int ofd, int nfd, bool errok) +{ + int rv; + + if (((rv = dup2(ofd, nfd)) < 0) && !errok && (errno != EBADF)) + errorf("too many files open in shell"); + +#ifdef __ultrix + /* XXX imake style */ + if (rv >= 0) + fcntl(nfd, F_SETFD, 0); +#endif + + return (rv); +} + +/* + * move fd from user space (0<=fd<10) to shell space (fd>=10), + * set close-on-exec flag. + */ +short +savefd(int fd) +{ + int nfd = fd; + + if (fd < FDBASE && (nfd = fcntl(fd, F_DUPFD, FDBASE)) < 0 && + errno == EBADF) + return (-1); + if (nfd < 0 || nfd > SHRT_MAX) + errorf("too many files open in shell"); + fcntl(nfd, F_SETFD, FD_CLOEXEC); + return ((short)nfd); +} + +void +restfd(int fd, int ofd) +{ + if (fd == 2) + shf_flush(&shf_iob[fd]); + if (ofd < 0) /* original fd closed */ + close(fd); + else if (fd != ofd) { + ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */ + close(ofd); + } +} + +void +openpipe(int *pv) +{ + int lpv[2]; + + if (pipe(lpv) < 0) + errorf("can't create pipe - try again"); + pv[0] = savefd(lpv[0]); + if (pv[0] != lpv[0]) + close(lpv[0]); + pv[1] = savefd(lpv[1]); + if (pv[1] != lpv[1]) + close(lpv[1]); +} + +void +closepipe(int *pv) +{ + close(pv[0]); + close(pv[1]); +} + +/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn + * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor. + */ +int +check_fd(const char *name, int mode, const char **emsgp) +{ + int fd, fl; + + if (name[0] == 'p' && !name[1]) + return (coproc_getfd(mode, emsgp)); + for (fd = 0; ksh_isdigit(*name); ++name) + fd = (fd * 10) + *name - '0'; + if (*name || fd >= FDBASE) { + if (emsgp) + *emsgp = "illegal file descriptor name"; + return (-1); + } + if ((fl = fcntl(fd, F_GETFL, 0)) < 0) { + if (emsgp) + *emsgp = "bad file descriptor"; + return (-1); + } + fl &= O_ACCMODE; + /* X_OK is a kludge to disable this check for dups (x<&1): + * historical shells never did this check (XXX don't know what + * POSIX has to say). + */ + if (!(mode & X_OK) && fl != O_RDWR && ( + ((mode & R_OK) && fl != O_RDONLY) || + ((mode & W_OK) && fl != O_WRONLY))) { + if (emsgp) + *emsgp = (fl == O_WRONLY) ? + "fd not open for reading" : + "fd not open for writing"; + return (-1); + } + return (fd); +} + +/* Called once from main */ +void +coproc_init(void) +{ + coproc.read = coproc.readw = coproc.write = -1; + coproc.njobs = 0; + coproc.id = 0; +} + +/* Called by c_read() when eof is read - close fd if it is the co-process fd */ +void +coproc_read_close(int fd) +{ + if (coproc.read >= 0 && fd == coproc.read) { + coproc_readw_close(fd); + close(coproc.read); + coproc.read = -1; + } +} + +/* Called by c_read() and by iosetup() to close the other side of the + * read pipe, so reads will actually terminate. + */ +void +coproc_readw_close(int fd) +{ + if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) { + close(coproc.readw); + coproc.readw = -1; + } +} + +/* Called by c_print when a write to a fd fails with EPIPE and by iosetup + * when co-process input is dup'd + */ +void +coproc_write_close(int fd) +{ + if (coproc.write >= 0 && fd == coproc.write) { + close(coproc.write); + coproc.write = -1; + } +} + +/* Called to check for existence of/value of the co-process file descriptor. + * (Used by check_fd() and by c_read/c_print to deal with -p option). + */ +int +coproc_getfd(int mode, const char **emsgp) +{ + int fd = (mode & R_OK) ? coproc.read : coproc.write; + + if (fd >= 0) + return (fd); + if (emsgp) + *emsgp = "no coprocess"; + return (-1); +} + +/* called to close file descriptors related to the coprocess (if any) + * Should be called with SIGCHLD blocked. + */ +void +coproc_cleanup(int reuse) +{ + /* This to allow co-processes to share output pipe */ + if (!reuse || coproc.readw < 0 || coproc.read < 0) { + if (coproc.read >= 0) { + close(coproc.read); + coproc.read = -1; + } + if (coproc.readw >= 0) { + close(coproc.readw); + coproc.readw = -1; + } + } + if (coproc.write >= 0) { + close(coproc.write); + coproc.write = -1; + } +} + +struct temp * +maketemp(Area *ap, Temp_type type, struct temp **tlist) +{ + struct temp *tp; + int len; + int fd; + char *pathname; + const char *dir; + + dir = tmpdir ? tmpdir : MKSH_DEFAULT_TMPDIR; +#if HAVE_MKSTEMP + len = strlen(dir) + 6 + 10 + 1; +#else + pathname = tempnam(dir, "mksh."); + len = ((pathname == NULL) ? 0 : strlen(pathname)) + 1; +#endif + tp = alloc(sizeof(struct temp) + len, ap); + tp->name = (char *)&tp[1]; +#if !HAVE_MKSTEMP + if (pathname == NULL) + tp->name[0] = '\0'; + else { + memcpy(tp->name, pathname, len); + free(pathname); + } +#endif + pathname = tp->name; + tp->shf = NULL; + tp->type = type; +#if HAVE_MKSTEMP + shf_snprintf(pathname, len, "%s/mksh.XXXXXXXXXX", dir); + if ((fd = mkstemp(pathname)) >= 0) +#else + if (tp->name[0] && (fd = open(tp->name, O_CREAT | O_RDWR, 0600)) >= 0) +#endif + tp->shf = shf_fdopen(fd, SHF_WR, NULL); + tp->pid = procpid; + + tp->next = *tlist; + *tlist = tp; + return (tp); +} + +/* + * We use a similar collision resolution algorithm as Python 2.5.4 + * but with a slightly tweaked implementation written from scratch. + */ + +#define INIT_TBLS 8 /* initial table size (power of 2) */ +#define PERTURB_SHIFT 5 /* see Python 2.5.4 Objects/dictobject.c */ + +static void texpand(struct table *, size_t); +static int tnamecmp(const void *, const void *); +static struct tbl *ktscan(struct table *, const char *, uint32_t, + struct tbl ***); + +static void +texpand(struct table *tp, size_t nsize) +{ + size_t i, j, osize = tp->size, perturb; + struct tbl *tblp, **pp; + struct tbl **ntblp, **otblp = tp->tbls; + + ntblp = alloc(nsize * sizeof(struct tbl *), tp->areap); + for (i = 0; i < nsize; i++) + ntblp[i] = NULL; + tp->size = nsize; + tp->nfree = (nsize * 4) / 5; /* table can get 80% full */ + tp->tbls = ntblp; + if (otblp == NULL) + return; + nsize--; /* from here on nsize := mask */ + for (i = 0; i < osize; i++) + if ((tblp = otblp[i]) != NULL) { + if ((tblp->flag & DEFINED)) { + /* search for free hash table slot */ + j = (perturb = tblp->ua.hval) & nsize; + goto find_first_empty_slot; + find_next_empty_slot: + j = (j << 2) + j + perturb + 1; + perturb >>= PERTURB_SHIFT; + find_first_empty_slot: + pp = &ntblp[j & nsize]; + if (*pp != NULL) + goto find_next_empty_slot; + /* found an empty hash table slot */ + *pp = tblp; + tp->nfree--; + } else if (!(tblp->flag & FINUSE)) { + afree(tblp, tp->areap); + } + } + afree(otblp, tp->areap); +} + +void +ktinit(struct table *tp, Area *ap, size_t tsize) +{ + tp->areap = ap; + tp->tbls = NULL; + tp->size = tp->nfree = 0; + if (tsize) + texpand(tp, tsize); +} + +/* table, name (key) to search for, hash(name), rv pointer to tbl ptr */ +static struct tbl * +ktscan(struct table *tp, const char *name, uint32_t h, struct tbl ***ppp) +{ + size_t j, perturb, mask; + struct tbl **pp, *p; + + mask = tp->size - 1; + /* search for hash table slot matching name */ + j = (perturb = h) & mask; + goto find_first_slot; + find_next_slot: + j = (j << 2) + j + perturb + 1; + perturb >>= PERTURB_SHIFT; + find_first_slot: + pp = &tp->tbls[j & mask]; + if ((p = *pp) != NULL && (p->ua.hval != h || !(p->flag & DEFINED) || + strcmp(p->name, name))) + goto find_next_slot; + /* p == NULL if not found, correct found entry otherwise */ + if (ppp) + *ppp = pp; + return (p); +} + +/* table, name (key) to search for, hash(n) */ +struct tbl * +ktsearch(struct table *tp, const char *n, uint32_t h) +{ + return (tp->size ? ktscan(tp, n, h, NULL) : NULL); +} + +/* table, name (key) to enter, hash(n) */ +struct tbl * +ktenter(struct table *tp, const char *n, uint32_t h) +{ + struct tbl **pp, *p; + int len; + + if (tp->size == 0) + texpand(tp, INIT_TBLS); + Search: + if ((p = ktscan(tp, n, h, &pp))) + return (p); + + if (tp->nfree <= 0) { + /* too full */ + texpand(tp, 2 * tp->size); + goto Search; + } + + /* create new tbl entry */ + len = strlen(n) + 1; + p = alloc(offsetof(struct tbl, name[0]) + len, tp->areap); + p->flag = 0; + p->type = 0; + p->areap = tp->areap; + p->ua.hval = h; + p->u2.field = 0; + p->u.array = NULL; + memcpy(p->name, n, len); + + /* enter in tp->tbls */ + tp->nfree--; + *pp = p; + return (p); +} + +void +ktwalk(struct tstate *ts, struct table *tp) +{ + ts->left = tp->size; + ts->next = tp->tbls; +} + +struct tbl * +ktnext(struct tstate *ts) +{ + while (--ts->left >= 0) { + struct tbl *p = *ts->next++; + if (p != NULL && (p->flag & DEFINED)) + return (p); + } + return (NULL); +} + +static int +tnamecmp(const void *p1, const void *p2) +{ + const struct tbl *a = *((const struct tbl * const *)p1); + const struct tbl *b = *((const struct tbl * const *)p2); + + return (strcmp(a->name, b->name)); +} + +struct tbl ** +ktsort(struct table *tp) +{ + size_t i; + struct tbl **p, **sp, **dp; + + p = alloc((tp->size + 1) * sizeof(struct tbl *), ATEMP); + sp = tp->tbls; /* source */ + dp = p; /* dest */ + i = (size_t)tp->size; + while (i--) + if ((*dp = *sp++) != NULL && (((*dp)->flag & DEFINED) || + ((*dp)->flag & ARRAY))) + dp++; + qsort(p, (i = dp - p), sizeof(void *), tnamecmp); + p[i] = NULL; + return (p); +} + +#ifdef SIGWINCH +static void +x_sigwinch(int sig MKSH_A_UNUSED) +{ + /* this runs inside interrupt context, with errno saved */ + + got_winch = 1; +} +#endif diff --git a/mksh/src/misc.c b/mksh/src/misc.c new file mode 100644 index 0000000..75a4de1 --- /dev/null +++ b/mksh/src/misc.c @@ -0,0 +1,1579 @@ +/* $OpenBSD: misc.c,v 1.37 2009/04/19 20:34:05 sthen Exp $ */ +/* $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" +#if !HAVE_GETRUSAGE +#include <sys/times.h> +#endif +#if HAVE_GRP_H +#include <grp.h> +#endif + +__RCSID("$MirOS: src/bin/mksh/misc.c,v 1.141 2010/07/17 22:09:36 tg Exp $"); + +unsigned char chtypes[UCHAR_MAX + 1]; /* type bits for unsigned char */ + +#if !HAVE_SETRESUGID +uid_t kshuid; +gid_t kshgid, kshegid; +#endif + +static int do_gmatch(const unsigned char *, const unsigned char *, + const unsigned char *, const unsigned char *); +static const unsigned char *cclass(const unsigned char *, int); +#ifdef TIOCSCTTY +static void chvt(const char *); +#endif + +/* + * Fast character classes + */ +void +setctypes(const char *s, int t) +{ + unsigned int i; + + if (t & C_IFS) { + for (i = 0; i < UCHAR_MAX + 1; i++) + chtypes[i] &= ~C_IFS; + chtypes[0] |= C_IFS; /* include \0 in C_IFS */ + } + while (*s != 0) + chtypes[(unsigned char)*s++] |= t; +} + +void +initctypes(void) +{ + int c; + + for (c = 'a'; c <= 'z'; c++) + chtypes[c] |= C_ALPHA; + for (c = 'A'; c <= 'Z'; c++) + chtypes[c] |= C_ALPHA; + chtypes['_'] |= C_ALPHA; + setctypes("0123456789", C_DIGIT); + setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */ + setctypes("*@#!$-?", C_VAR1); + setctypes(" \t\n", C_IFSWS); + setctypes("=-+?", C_SUBOP1); + setctypes("\t\n \"#$&'()*;<=>?[\\]`|", C_QUOTE); +} + +/* called from XcheckN() to grow buffer */ +char * +Xcheck_grow_(XString *xsp, const char *xp, unsigned int more) +{ + const char *old_beg = xsp->beg; + + xsp->len += more > xsp->len ? more : xsp->len; + xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap); + xsp->end = xsp->beg + xsp->len; + return (xsp->beg + (xp - old_beg)); +} + +#define SHFLAGS_DEFNS +#include "sh_flags.h" + +const struct shoption options[] = { +#define SHFLAGS_ITEMS +#include "sh_flags.h" +}; + +/* + * translate -o option into F* constant (also used for test -o option) + */ +size_t +option(const char *n) +{ + size_t i; + + if ((n[0] == '-' || n[0] == '+') && n[1] && !n[2]) { + for (i = 0; i < NELEM(options); i++) + if (options[i].c == n[1]) + return (i); + } else for (i = 0; i < NELEM(options); i++) + if (options[i].name && strcmp(options[i].name, n) == 0) + return (i); + + return ((size_t)-1); +} + +struct options_info { + int opt_width; + int opts[NELEM(options)]; +}; + +static char *options_fmt_entry(char *, int, int, const void *); +static void printoptions(bool); + +/* format a single select menu item */ +static char * +options_fmt_entry(char *buf, int buflen, int i, const void *arg) +{ + const struct options_info *oi = (const struct options_info *)arg; + + shf_snprintf(buf, buflen, "%-*s %s", + oi->opt_width, options[oi->opts[i]].name, + Flag(oi->opts[i]) ? "on" : "off"); + return (buf); +} + +static void +printoptions(bool verbose) +{ + int i = 0; + + if (verbose) { + int n = 0, len, octs = 0; + struct options_info oi; + + /* verbose version */ + shf_puts("Current option settings\n", shl_stdout); + + oi.opt_width = 0; + while (i < (int)NELEM(options)) { + if (options[i].name) { + oi.opts[n++] = i; + len = strlen(options[i].name); + if (len > octs) + octs = len; + len = utf_mbswidth(options[i].name); + if (len > oi.opt_width) + oi.opt_width = len; + } + ++i; + } + print_columns(shl_stdout, n, options_fmt_entry, &oi, + octs + 4, oi.opt_width + 4, true); + } else { + /* short version á la AT&T ksh93 */ + shf_puts("set", shl_stdout); + while (i < (int)NELEM(options)) { + if (Flag(i) && options[i].name) + shprintf(" -o %s", options[i].name); + ++i; + } + shf_putc('\n', shl_stdout); + } +} + +char * +getoptions(void) +{ + unsigned int i; + char m[(int) FNFLAGS + 1]; + char *cp = m; + + for (i = 0; i < NELEM(options); i++) + if (options[i].c && Flag(i)) + *cp++ = options[i].c; + strndupx(cp, m, cp - m, ATEMP); + return (cp); +} + +/* change a Flag(*) value; takes care of special actions */ +void +change_flag(enum sh_flag f, int what, unsigned int newval) +{ + unsigned char oldval; + + oldval = Flag(f); + Flag(f) = newval ? 1 : 0; /* needed for tristates */ +#ifndef MKSH_UNEMPLOYED + if (f == FMONITOR) { + if (what != OF_CMDLINE && newval != oldval) + j_change(); + } else +#endif + if (( +#if !MKSH_S_NOVI + f == FVI || +#endif + f == FEMACS || f == FGMACS) && newval) { +#if !MKSH_S_NOVI + Flag(FVI) = +#endif + Flag(FEMACS) = Flag(FGMACS) = 0; + Flag(f) = (unsigned char)newval; + } else if (f == FPRIVILEGED && oldval && !newval) { + /* Turning off -p? */ +#if HAVE_SETRESUGID + gid_t kshegid = getgid(); + + setresgid(kshegid, kshegid, kshegid); +#if HAVE_SETGROUPS + setgroups(1, &kshegid); +#endif + setresuid(ksheuid, ksheuid, ksheuid); +#else + seteuid(ksheuid = kshuid = getuid()); + setuid(ksheuid); + setegid(kshegid = kshgid = getgid()); + setgid(kshegid); +#endif + } else if ((f == FPOSIX || f == FSH) && newval) { + Flag(FPOSIX) = Flag(FSH) = Flag(FBRACEEXPAND) = 0; + Flag(f) = (unsigned char)newval; + } + /* Changing interactive flag? */ + if (f == FTALKING) { + if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid) + Flag(FTALKING_I) = (unsigned char)newval; + } +} + +/* Parse command line & set command arguments. Returns the index of + * non-option arguments, -1 if there is an error. + */ +int +parse_args(const char **argv, + int what, /* OF_CMDLINE or OF_SET */ + bool *setargsp) +{ + static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */ + static char set_opts[NELEM(options) + 6]; /* A:o;s\0 */ + char set, *opts; + const char *array = NULL; + Getopt go; + size_t i; + int optc, sortargs = 0, arrayset = 0; + + /* First call? Build option strings... */ + if (cmd_opts[0] == '\0') { + char *p = cmd_opts, *q = set_opts; + + /* see cmd_opts[] declaration */ + *p++ = 'o'; + *p++ = ':'; +#if !defined(MKSH_SMALL) || defined(TIOCSCTTY) + *p++ = 'T'; + *p++ = ':'; +#endif + /* see set_opts[] declaration */ + *q++ = 'A'; + *q++ = ':'; + *q++ = 'o'; + *q++ = ';'; + *q++ = 's'; + + for (i = 0; i < NELEM(options); i++) { + if (options[i].c) { + if (options[i].flags & OF_CMDLINE) + *p++ = options[i].c; + if (options[i].flags & OF_SET) + *q++ = options[i].c; + } + } + *p = '\0'; + *q = '\0'; + } + + if (what == OF_CMDLINE) { + const char *p = argv[0], *q; + /* Set FLOGIN before parsing options so user can clear + * flag using +l. + */ + if (*p != '-') + for (q = p; *q; ) + if (*q++ == '/') + p = q; + Flag(FLOGIN) = (*p == '-'); + opts = cmd_opts; + } else if (what == OF_FIRSTTIME) { + opts = cmd_opts; + } else + opts = set_opts; + ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT); + while ((optc = ksh_getopt(argv, &go, opts)) != -1) { + set = (go.info & GI_PLUS) ? 0 : 1; + switch (optc) { + case 'A': + if (what == OF_FIRSTTIME) + break; + arrayset = set ? 1 : -1; + array = go.optarg; + break; + + case 'o': + if (what == OF_FIRSTTIME) + break; + if (go.optarg == NULL) { + /* lone -o: print options + * + * Note that on the command line, -o requires + * an option (ie, can't get here if what is + * OF_CMDLINE). + */ + printoptions(set); + break; + } + i = option(go.optarg); + if ((enum sh_flag)i == FARC4RANDOM) { + warningf(true, "Do not use set ±o arc4random," + " it will be removed in the next version" + " of mksh!"); + return (0); + } + if ((i != (size_t)-1) && set == Flag(i)) + /* Don't check the context if the flag + * isn't changing - makes "set -o interactive" + * work if you're already interactive. Needed + * if the output of "set +o" is to be used. + */ + ; + else if ((i != (size_t)-1) && (options[i].flags & what)) + change_flag((enum sh_flag)i, what, set); + else { + bi_errorf("%s: bad option", go.optarg); + return (-1); + } + break; + +#if !defined(MKSH_SMALL) || defined(TIOCSCTTY) + case 'T': + if (what != OF_FIRSTTIME) + break; +#ifndef TIOCSCTTY + errorf("no TIOCSCTTY ioctl"); +#else + change_flag(FTALKING, OF_CMDLINE, 1); + chvt(go.optarg); + break; +#endif +#endif + + case '?': + return (-1); + + default: + if (what == OF_FIRSTTIME) + break; + /* -s: sort positional params (AT&T ksh stupidity) */ + if (what == OF_SET && optc == 's') { + sortargs = 1; + break; + } + for (i = 0; i < NELEM(options); i++) + if (optc == options[i].c && + (what & options[i].flags)) { + change_flag((enum sh_flag)i, what, set); + break; + } + if (i == NELEM(options)) + internal_errorf("parse_args: '%c'", optc); + } + } + if (!(go.info & GI_MINUSMINUS) && argv[go.optind] && + (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') && + argv[go.optind][1] == '\0') { + /* lone - clears -v and -x flags */ + if (argv[go.optind][0] == '-') + Flag(FVERBOSE) = Flag(FXTRACE) = 0; + /* set skips lone - or + option */ + go.optind++; + } + if (setargsp) + /* -- means set $#/$* even if there are no arguments */ + *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) || + argv[go.optind]); + + if (arrayset && (!*array || *skip_varname(array, false))) { + bi_errorf("%s: is not an identifier", array); + return (-1); + } + if (sortargs) { + for (i = go.optind; argv[i]; i++) + ; + qsort(&argv[go.optind], i - go.optind, sizeof(void *), + xstrcmp); + } + if (arrayset) + go.optind += set_array(array, arrayset > 0 ? true : false, + argv + go.optind); + + return (go.optind); +} + +/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */ +int +getn(const char *s, int *ai) +{ + int i, c, rv = 0; + bool neg = false; + + do { + c = *s++; + } while (ksh_isspace(c)); + if (c == '-') { + neg = true; + c = *s++; + } else if (c == '+') + c = *s++; + *ai = i = 0; + do { + if (!ksh_isdigit(c)) + goto getn_out; + i *= 10; + if (i < *ai) + /* overflow */ + goto getn_out; + i += c - '0'; + *ai = i; + } while ((c = *s++)); + rv = 1; + + getn_out: + if (neg) + *ai = -*ai; + return (rv); +} + +/* getn() that prints error */ +int +bi_getn(const char *as, int *ai) +{ + int rv; + + if (!(rv = getn(as, ai))) + bi_errorf("%s: bad number", as); + return (rv); +} + +/* -------- gmatch.c -------- */ + +/* + * int gmatch(string, pattern) + * char *string, *pattern; + * + * Match a pattern as in sh(1). + * pattern character are prefixed with MAGIC by expand. + */ + +int +gmatchx(const char *s, const char *p, bool isfile) +{ + const char *se, *pe; + + if (s == NULL || p == NULL) + return (0); + + se = s + strlen(s); + pe = p + strlen(p); + /* isfile is false iff no syntax check has been done on + * the pattern. If check fails, just to a strcmp(). + */ + if (!isfile && !has_globbing(p, pe)) { + size_t len = pe - p + 1; + char tbuf[64]; + char *t = len <= sizeof(tbuf) ? tbuf : alloc(len, ATEMP); + debunk(t, p, len); + return (!strcmp(t, s)); + } + return (do_gmatch((const unsigned char *) s, (const unsigned char *) se, + (const unsigned char *) p, (const unsigned char *) pe)); +} + +/* Returns if p is a syntacticly correct globbing pattern, false + * if it contains no pattern characters or if there is a syntax error. + * Syntax errors are: + * - [ with no closing ] + * - imbalanced $(...) expression + * - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d)) + */ +/*XXX +- if no magic, + if dest given, copy to dst + return ? +- if magic && (no globbing || syntax error) + debunk to dst + return ? +- return ? +*/ +int +has_globbing(const char *xp, const char *xpe) +{ + const unsigned char *p = (const unsigned char *) xp; + const unsigned char *pe = (const unsigned char *) xpe; + int c; + int nest = 0, bnest = 0; + int saw_glob = 0; + int in_bracket = 0; /* inside [...] */ + + for (; p < pe; p++) { + if (!ISMAGIC(*p)) + continue; + if ((c = *++p) == '*' || c == '?') + saw_glob = 1; + else if (c == '[') { + if (!in_bracket) { + saw_glob = 1; + in_bracket = 1; + if (ISMAGIC(p[1]) && p[2] == NOT) + p += 2; + if (ISMAGIC(p[1]) && p[2] == ']') + p += 2; + } + /* XXX Do we need to check ranges here? POSIX Q */ + } else if (c == ']') { + if (in_bracket) { + if (bnest) /* [a*(b]) */ + return (0); + in_bracket = 0; + } + } else if ((c & 0x80) && vstrchr("*+?@! ", c & 0x7f)) { + saw_glob = 1; + if (in_bracket) + bnest++; + else + nest++; + } else if (c == '|') { + if (in_bracket && !bnest) /* *(a[foo|bar]) */ + return (0); + } else if (c == /*(*/ ')') { + if (in_bracket) { + if (!bnest--) /* *(a[b)c] */ + return (0); + } else if (nest) + nest--; + } + /* + * else must be a MAGIC-MAGIC, or MAGIC-!, + * MAGIC--, MAGIC-], MAGIC-{, MAGIC-, MAGIC-} + */ + } + return (saw_glob && !in_bracket && !nest); +} + +/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */ +static int +do_gmatch(const unsigned char *s, const unsigned char *se, + const unsigned char *p, const unsigned char *pe) +{ + int sc, pc; + const unsigned char *prest, *psub, *pnext; + const unsigned char *srest; + + if (s == NULL || p == NULL) + return (0); + while (p < pe) { + pc = *p++; + sc = s < se ? *s : '\0'; + s++; + if (!ISMAGIC(pc)) { + if (sc != pc) + return (0); + continue; + } + switch (*p++) { + case '[': + if (sc == 0 || (p = cclass(p, sc)) == NULL) + return (0); + break; + + case '?': + if (sc == 0) + return (0); + if (UTFMODE) { + --s; + s += utf_ptradj((const void *)s); + } + break; + + case '*': + if (p == pe) + return (1); + s--; + do { + if (do_gmatch(s, se, p, pe)) + return (1); + } while (s++ < se); + return (0); + + /** + * [*+?@!](pattern|pattern|..) + * This is also needed for ${..%..}, etc. + */ + case 0x80|'+': /* matches one or more times */ + case 0x80|'*': /* matches zero or more times */ + if (!(prest = pat_scan(p, pe, 0))) + return (0); + s--; + /* take care of zero matches */ + if (p[-1] == (0x80 | '*') && + do_gmatch(s, se, prest, pe)) + return (1); + for (psub = p; ; psub = pnext) { + pnext = pat_scan(psub, pe, 1); + for (srest = s; srest <= se; srest++) { + if (do_gmatch(s, srest, psub, pnext - 2) && + (do_gmatch(srest, se, prest, pe) || + (s != srest && do_gmatch(srest, + se, p - 2, pe)))) + return (1); + } + if (pnext == prest) + break; + } + return (0); + + case 0x80|'?': /* matches zero or once */ + case 0x80|'@': /* matches one of the patterns */ + case 0x80|' ': /* simile for @ */ + if (!(prest = pat_scan(p, pe, 0))) + return (0); + s--; + /* Take care of zero matches */ + if (p[-1] == (0x80 | '?') && + do_gmatch(s, se, prest, pe)) + return (1); + for (psub = p; ; psub = pnext) { + pnext = pat_scan(psub, pe, 1); + srest = prest == pe ? se : s; + for (; srest <= se; srest++) { + if (do_gmatch(s, srest, psub, pnext - 2) && + do_gmatch(srest, se, prest, pe)) + return (1); + } + if (pnext == prest) + break; + } + return (0); + + case 0x80|'!': /* matches none of the patterns */ + if (!(prest = pat_scan(p, pe, 0))) + return (0); + s--; + for (srest = s; srest <= se; srest++) { + int matched = 0; + + for (psub = p; ; psub = pnext) { + pnext = pat_scan(psub, pe, 1); + if (do_gmatch(s, srest, psub, + pnext - 2)) { + matched = 1; + break; + } + if (pnext == prest) + break; + } + if (!matched && + do_gmatch(srest, se, prest, pe)) + return (1); + } + return (0); + + default: + if (sc != p[-1]) + return (0); + break; + } + } + return (s == se); +} + +static const unsigned char * +cclass(const unsigned char *p, int sub) +{ + int c, d, notp, found = 0; + const unsigned char *orig_p = p; + + if ((notp = (ISMAGIC(*p) && *++p == NOT))) + p++; + do { + c = *p++; + if (ISMAGIC(c)) { + c = *p++; + if ((c & 0x80) && !ISMAGIC(c)) { + c &= 0x7f;/* extended pattern matching: *+?@! */ + /* XXX the ( char isn't handled as part of [] */ + if (c == ' ') /* simile for @: plain (..) */ + c = '(' /*)*/; + } + } + if (c == '\0') + /* No closing ] - act as if the opening [ was quoted */ + return (sub == '[' ? orig_p : NULL); + if (ISMAGIC(p[0]) && p[1] == '-' && + (!ISMAGIC(p[2]) || p[3] != ']')) { + p += 2; /* MAGIC- */ + d = *p++; + if (ISMAGIC(d)) { + d = *p++; + if ((d & 0x80) && !ISMAGIC(d)) + d &= 0x7f; + } + /* POSIX says this is an invalid expression */ + if (c > d) + return (NULL); + } else + d = c; + if (c == sub || (c <= sub && sub <= d)) + found = 1; + } while (!(ISMAGIC(p[0]) && p[1] == ']')); + + return ((found != notp) ? p+2 : NULL); +} + +/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */ +const unsigned char * +pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep) +{ + int nest = 0; + + for (; p < pe; p++) { + if (!ISMAGIC(*p)) + continue; + if ((*++p == /*(*/ ')' && nest-- == 0) || + (*p == '|' && match_sep && nest == 0)) + return (p + 1); + if ((*p & 0x80) && vstrchr("*+?@! ", *p & 0x7f)) + nest++; + } + return (NULL); +} + +int +xstrcmp(const void *p1, const void *p2) +{ + return (strcmp(*(const char * const *)p1, *(const char * const *)p2)); +} + +/* Initialise a Getopt structure */ +void +ksh_getopt_reset(Getopt *go, int flags) +{ + go->optind = 1; + go->optarg = NULL; + go->p = 0; + go->flags = flags; + go->info = 0; + go->buf[1] = '\0'; +} + + +/* getopt() used for shell built-in commands, the getopts command, and + * command line options. + * A leading ':' in options means don't print errors, instead return '?' + * or ':' and set go->optarg to the offending option character. + * If GF_ERROR is set (and option doesn't start with :), errors result in + * a call to bi_errorf(). + * + * Non-standard features: + * - ';' is like ':' in options, except the argument is optional + * (if it isn't present, optarg is set to 0). + * Used for 'set -o'. + * - ',' is like ':' in options, except the argument always immediately + * follows the option character (optarg is set to the null string if + * the option is missing). + * Used for 'read -u2', 'print -u2' and fc -40. + * - '#' is like ':' in options, expect that the argument is optional + * and must start with a digit. If the argument doesn't start with a + * digit, it is assumed to be missing and normal option processing + * continues (optarg is set to 0 if the option is missing). + * Used for 'typeset -LZ4'. + * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an + * option starting with + is accepted, the GI_PLUS flag will be set + * in go->info. + */ +int +ksh_getopt(const char **argv, Getopt *go, const char *optionsp) +{ + char c; + const char *o; + + if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') { + const char *arg = argv[go->optind], flag = arg ? *arg : '\0'; + + go->p = 1; + if (flag == '-' && arg[1] == '-' && arg[2] == '\0') { + go->optind++; + go->p = 0; + go->info |= GI_MINUSMINUS; + return (-1); + } + if (arg == NULL || + ((flag != '-' ) && /* neither a - nor a + (if + allowed) */ + (!(go->flags & GF_PLUSOPT) || flag != '+')) || + (c = arg[1]) == '\0') { + go->p = 0; + return (-1); + } + go->optind++; + go->info &= ~(GI_MINUS|GI_PLUS); + go->info |= flag == '-' ? GI_MINUS : GI_PLUS; + } + go->p++; + if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' || + !(o = cstrchr(optionsp, c))) { + if (optionsp[0] == ':') { + go->buf[0] = c; + go->optarg = go->buf; + } else { + warningf(true, "%s%s-%c: unknown option", + (go->flags & GF_NONAME) ? "" : argv[0], + (go->flags & GF_NONAME) ? "" : ": ", c); + if (go->flags & GF_ERROR) + bi_errorfz(); + } + return ('?'); + } + /* : means argument must be present, may be part of option argument + * or the next argument + * ; same as : but argument may be missing + * , means argument is part of option argument, and may be null. + */ + if (*++o == ':' || *o == ';') { + if (argv[go->optind - 1][go->p]) + go->optarg = argv[go->optind - 1] + go->p; + else if (argv[go->optind]) + go->optarg = argv[go->optind++]; + else if (*o == ';') + go->optarg = NULL; + else { + if (optionsp[0] == ':') { + go->buf[0] = c; + go->optarg = go->buf; + return (':'); + } + warningf(true, "%s%s-'%c' requires argument", + (go->flags & GF_NONAME) ? "" : argv[0], + (go->flags & GF_NONAME) ? "" : ": ", c); + if (go->flags & GF_ERROR) + bi_errorfz(); + return ('?'); + } + go->p = 0; + } else if (*o == ',') { + /* argument is attached to option character, even if null */ + go->optarg = argv[go->optind - 1] + go->p; + go->p = 0; + } else if (*o == '#') { + /* argument is optional and may be attached or unattached + * but must start with a digit. optarg is set to 0 if the + * argument is missing. + */ + if (argv[go->optind - 1][go->p]) { + if (ksh_isdigit(argv[go->optind - 1][go->p])) { + go->optarg = argv[go->optind - 1] + go->p; + go->p = 0; + } else + go->optarg = NULL; + } else { + if (argv[go->optind] && ksh_isdigit(argv[go->optind][0])) { + go->optarg = argv[go->optind++]; + go->p = 0; + } else + go->optarg = NULL; + } + } + return (c); +} + +/* print variable/alias value using necessary quotes + * (POSIX says they should be suitable for re-entry...) + * No trailing newline is printed. + */ +void +print_value_quoted(const char *s) +{ + const char *p; + int inquote = 0; + + /* Test if any quotes are needed */ + for (p = s; *p; p++) + if (ctype(*p, C_QUOTE)) + break; + if (!*p) { + shf_puts(s, shl_stdout); + return; + } + for (p = s; *p; p++) { + if (*p == '\'') { + if (inquote) + shf_putc('\'', shl_stdout); + shf_putc('\\', shl_stdout); + inquote = 0; + } else if (!inquote) { + shf_putc('\'', shl_stdout); + inquote = 1; + } + shf_putc(*p, shl_stdout); + } + if (inquote) + shf_putc('\'', shl_stdout); +} + +/* + * Print things in columns and rows - func() is called to format + * the i-th element + */ +void +print_columns(struct shf *shf, int n, + char *(*func)(char *, int, int, const void *), + const void *arg, int max_oct, int max_col, bool prefcol) +{ + int i, r, c, rows, cols, nspace; + char *str; + + if (n <= 0) { +#ifndef MKSH_SMALL + internal_warningf("print_columns called with n=%d <= 0", n); +#endif + return; + } + + ++max_oct; + str = alloc(max_oct, ATEMP); + + /* ensure x_cols is valid first */ + if (x_cols < MIN_COLS) + change_winsz(); + + /* + * We use (max_col + 1) to consider the space separator. + * Note that no space is printed after the last column + * to avoid problems with terminals that have auto-wrap. + */ + cols = x_cols / (max_col + 1); + + /* if we can only print one column anyway, skip the goo */ + if (cols < 2) { + for (i = 0; i < n; ++i) + shf_fprintf(shf, "%s \n", + (*func)(str, max_oct, i, arg)); + goto out; + } + + rows = (n + cols - 1) / cols; + if (prefcol && cols > rows) { + i = rows; + rows = cols > n ? n : cols; + cols = i; + } + + max_col = -max_col; + nspace = (x_cols + max_col * cols) / cols; + if (nspace <= 0) + nspace = 1; + for (r = 0; r < rows; r++) { + for (c = 0; c < cols; c++) { + i = c * rows + r; + if (i < n) { + shf_fprintf(shf, "%*s", max_col, + (*func)(str, max_oct, i, arg)); + if (c + 1 < cols) + shf_fprintf(shf, "%*s", nspace, null); + } + } + shf_putchar('\n', shf); + } + out: + afree(str, ATEMP); +} + +/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */ +void +strip_nuls(char *buf, int nbytes) +{ + char *dst; + + /* nbytes check because some systems (older FreeBSDs) have a buggy + * memchr() + */ + if (nbytes && (dst = memchr(buf, '\0', nbytes))) { + char *end = buf + nbytes; + char *p, *q; + + for (p = dst; p < end; p = q) { + /* skip a block of nulls */ + while (++p < end && *p == '\0') + ; + /* find end of non-null block */ + if (!(q = memchr(p, '\0', end - p))) + q = end; + memmove(dst, p, q - p); + dst += q - p; + } + *dst = '\0'; + } +} + +/* Like read(2), but if read fails due to non-blocking flag, resets flag + * and restarts read. + */ +int +blocking_read(int fd, char *buf, int nbytes) +{ + int ret; + int tried_reset = 0; + + while ((ret = read(fd, buf, nbytes)) < 0) { + if (!tried_reset && errno == EAGAIN) { + if (reset_nonblock(fd) > 0) { + tried_reset = 1; + continue; + } + errno = EAGAIN; + } + break; + } + return (ret); +} + +/* Reset the non-blocking flag on the specified file descriptor. + * Returns -1 if there was an error, 0 if non-blocking wasn't set, + * 1 if it was. + */ +int +reset_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + return (-1); + if (!(flags & O_NONBLOCK)) + return (0); + flags &= ~O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + return (-1); + return (1); +} + + +/* Like getcwd(), except bsize is ignored if buf is 0 (PATH_MAX is used) */ +char * +ksh_get_wd(size_t *dlen) +{ + char *ret, *b; + size_t len = 1; + +#ifdef NO_PATH_MAX + if ((b = get_current_dir_name())) { + len = strlen(b) + 1; + strndupx(ret, b, len - 1, ATEMP); + free(b); + } else + ret = NULL; +#else + if ((ret = getcwd((b = alloc(PATH_MAX + 1, ATEMP)), PATH_MAX))) + ret = aresize(b, len = (strlen(b) + 1), ATEMP); + else + afree(b, ATEMP); +#endif + + if (dlen) + *dlen = len; + return (ret); +} + +/* + * Makes a filename into result using the following algorithm. + * - make result NULL + * - if file starts with '/', append file to result & set cdpathp to NULL + * - if file starts with ./ or ../ append cwd and file to result + * and set cdpathp to NULL + * - if the first element of cdpathp doesnt start with a '/' xx or '.' xx + * then cwd is appended to result. + * - the first element of cdpathp is appended to result + * - file is appended to result + * - cdpathp is set to the start of the next element in cdpathp (or NULL + * if there are no more elements. + * The return value indicates whether a non-null element from cdpathp + * was appended to result. + */ +int +make_path(const char *cwd, const char *file, + char **cdpathp, /* & of : separated list */ + XString *xsp, + int *phys_pathp) +{ + int rval = 0; + bool use_cdpath = true; + char *plist; + int len, plen = 0; + char *xp = Xstring(*xsp, xp); + + if (!file) + file = null; + + if (file[0] == '/') { + *phys_pathp = 0; + use_cdpath = false; + } else { + if (file[0] == '.') { + char c = file[1]; + + if (c == '.') + c = file[2]; + if (c == '/' || c == '\0') + use_cdpath = false; + } + + plist = *cdpathp; + if (!plist) + use_cdpath = false; + else if (use_cdpath) { + char *pend; + + for (pend = plist; *pend && *pend != ':'; pend++) + ; + plen = pend - plist; + *cdpathp = *pend ? pend + 1 : NULL; + } + + if ((!use_cdpath || !plen || plist[0] != '/') && + (cwd && *cwd)) { + len = strlen(cwd); + XcheckN(*xsp, xp, len); + memcpy(xp, cwd, len); + xp += len; + if (cwd[len - 1] != '/') + Xput(*xsp, xp, '/'); + } + *phys_pathp = Xlength(*xsp, xp); + if (use_cdpath && plen) { + XcheckN(*xsp, xp, plen); + memcpy(xp, plist, plen); + xp += plen; + if (plist[plen - 1] != '/') + Xput(*xsp, xp, '/'); + rval = 1; + } + } + + len = strlen(file) + 1; + XcheckN(*xsp, xp, len); + memcpy(xp, file, len); + + if (!use_cdpath) + *cdpathp = NULL; + + return (rval); +} + +/* + * Simplify pathnames containing "." and ".." entries. + * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b" + */ +void +simplify_path(char *pathl) +{ + char *cur, *t; + bool isrooted; + char *very_start = pathl, *start; + + if (!*pathl) + return; + + if ((isrooted = pathl[0] == '/')) + very_start++; + + /* Before After + * /foo/ /foo + * /foo/../../bar /bar + * /foo/./blah/.. /foo + * . . + * .. .. + * ./foo foo + * foo/../../../bar ../../bar + */ + + for (cur = t = start = very_start; ; ) { + /* treat multiple '/'s as one '/' */ + while (*t == '/') + t++; + + if (*t == '\0') { + if (cur == pathl) + /* convert empty path to dot */ + *cur++ = '.'; + *cur = '\0'; + break; + } + + if (t[0] == '.') { + if (!t[1] || t[1] == '/') { + t += 1; + continue; + } else if (t[1] == '.' && (!t[2] || t[2] == '/')) { + if (!isrooted && cur == start) { + if (cur != very_start) + *cur++ = '/'; + *cur++ = '.'; + *cur++ = '.'; + start = cur; + } else if (cur != start) + while (--cur > start && *cur != '/') + ; + t += 2; + continue; + } + } + + if (cur != very_start) + *cur++ = '/'; + + /* find/copy next component of pathname */ + while (*t && *t != '/') + *cur++ = *t++; + } +} + + +void +set_current_wd(char *pathl) +{ + size_t len = 1; + char *p = pathl; + + if (p == NULL) { + if ((p = ksh_get_wd(&len)) == NULL) + p = null; + } else + len = strlen(p) + 1; + + if (len > current_wd_size) { + afree(current_wd, APERM); + current_wd = alloc(current_wd_size = len, APERM); + } + memcpy(current_wd, p, len); + if (p != pathl && p != null) + afree(p, ATEMP); +} + +#ifdef TIOCSCTTY +extern void chvt_reinit(void); + +static void +chvt(const char *fn) +{ + char dv[20]; + struct stat sb; + int fd; + + /* for entropy */ + kshstate_f.h = evilhash(fn); + + if (*fn == '-') { + memcpy(dv, "-/dev/null", sizeof("-/dev/null")); + fn = dv + 1; + } else { + if (stat(fn, &sb)) { + memcpy(dv, "/dev/ttyC", 9); + strlcpy(dv + 9, fn, sizeof(dv) - 9); + if (stat(dv, &sb)) { + strlcpy(dv + 8, fn, sizeof(dv) - 8); + if (stat(dv, &sb)) + errorf("chvt: can't find tty %s", fn); + } + fn = dv; + } + if (!(sb.st_mode & S_IFCHR)) + errorf("chvt: not a char device: %s", fn); + if ((sb.st_uid != 0) && chown(fn, 0, 0)) + warningf(false, "chvt: cannot chown root %s", fn); + if (((sb.st_mode & 07777) != 0600) && chmod(fn, (mode_t)0600)) + warningf(false, "chvt: cannot chmod 0600 %s", fn); +#if HAVE_REVOKE + if (revoke(fn)) +#endif + warningf(false, "chvt: cannot revoke %s, new shell is" + " potentially insecure", fn); + } + if ((fd = open(fn, O_RDWR)) == -1) { + sleep(1); + if ((fd = open(fn, O_RDWR)) == -1) + errorf("chvt: cannot open %s", fn); + } + switch (fork()) { + case -1: + errorf("chvt: %s failed", "fork"); + case 0: + break; + default: + exit(0); + } + if (setsid() == -1) + errorf("chvt: %s failed", "setsid"); + if (fn != dv + 1) { + if (ioctl(fd, TIOCSCTTY, NULL) == -1) + errorf("chvt: %s failed", "TIOCSCTTY"); + if (tcflush(fd, TCIOFLUSH)) + errorf("chvt: %s failed", "TCIOFLUSH"); + } + ksh_dup2(fd, 0, false); + ksh_dup2(fd, 1, false); + ksh_dup2(fd, 2, false); + if (fd > 2) + close(fd); + chvt_reinit(); +} +#endif + +#ifdef DEBUG +char longsizes_are_okay[sizeof(long) == sizeof(unsigned long) ? 1 : -1]; +char arisize_is_okay[sizeof(mksh_ari_t) == 4 ? 1 : -1]; +char uarisize_is_okay[sizeof(mksh_uari_t) == 4 ? 1 : -1]; + +char * +strchr(char *p, int ch) +{ + for (;; ++p) { + if (*p == ch) + return (p); + if (!*p) + return (NULL); + } + /* NOTREACHED */ +} + +char * +strstr(char *b, const char *l) +{ + char first, c; + size_t n; + + if ((first = *l++) == '\0') + return (b); + n = strlen(l); + strstr_look: + while ((c = *b++) != first) + if (c == '\0') + return (NULL); + if (strncmp(b, l, n)) + goto strstr_look; + return (b - 1); +} +#endif + +#ifndef MKSH_ASSUME_UTF8 +#if !HAVE_STRCASESTR +const char * +stristr(const char *b, const char *l) +{ + char first, c; + size_t n; + + if ((first = *l++), ((first = ksh_tolower(first)) == '\0')) + return (b); + n = strlen(l); + stristr_look: + while ((c = *b++), ((c = ksh_tolower(c)) != first)) + if (c == '\0') + return (NULL); + if (strncasecmp(b, l, n)) + goto stristr_look; + return (b - 1); +} +#endif +#endif + +#ifdef MKSH_SMALL +char * +strndup_(const char *src, size_t len, Area *ap) +{ + char *dst = NULL; + + if (src != NULL) { + dst = alloc(len + 1, ap); + memcpy(dst, src, len); + dst[len] = '\0'; + } + return (dst); +} + +char * +strdup_(const char *src, Area *ap) +{ + return (src == NULL ? NULL : strndup_(src, strlen(src), ap)); +} +#endif + +#if !HAVE_GETRUSAGE +#define INVTCK(r,t) do { \ + r.tv_usec = ((t) % (1000000 / CLK_TCK)) * (1000000 / CLK_TCK); \ + r.tv_sec = (t) / CLK_TCK; \ +} while (/* CONSTCOND */ 0) + +int +getrusage(int what, struct rusage *ru) +{ + struct tms tms; + clock_t u, s; + + if (/* ru == NULL || */ times(&tms) == (clock_t)-1) + return (-1); + + switch (what) { + case RUSAGE_SELF: + u = tms.tms_utime; + s = tms.tms_stime; + break; + case RUSAGE_CHILDREN: + u = tms.tms_cutime; + s = tms.tms_cstime; + break; + default: + errno = EINVAL; + return (-1); + } + INVTCK(ru->ru_utime, u); + INVTCK(ru->ru_stime, s); + return (0); +} +#endif + +/* + * process the string available via fg (get a char) + * and fp (put back a char) for backslash escapes, + * assuming the first call to *fg gets the char di- + * rectly after the backslash; return the character + * (0..0xFF), Unicode (wc + 0x100), or -1 if no known + * escape sequence was found + */ +int +unbksl(bool cstyle, int (*fg)(void), void (*fp)(int)) +{ + int wc, i, c, fc; + + fc = (*fg)(); + switch (fc) { + case 'a': + /* + * according to the comments in pdksh, \007 seems + * to be more portable than \a (due to HP-UX cc, + * Ultrix cc, old pcc, etc.) so we avoid the escape + * sequence altogether in mksh and assume ASCII + */ + wc = 7; + break; + case 'b': + wc = '\b'; + break; + case 'c': + if (!cstyle) + goto unknown_escape; + c = (*fg)(); + wc = CTRL(c); + break; + case 'E': + case 'e': + wc = 033; + break; + case 'f': + wc = '\f'; + break; + case 'n': + wc = '\n'; + break; + case 'r': + wc = '\r'; + break; + case 't': + wc = '\t'; + break; + case 'v': + /* assume ASCII here as well */ + wc = 11; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + if (!cstyle) + goto unknown_escape; + /* FALLTHROUGH */ + case '0': + if (cstyle) + (*fp)(fc); + /* + * look for an octal number with up to three + * digits, not counting the leading zero; + * convert it to a raw octet + */ + wc = 0; + i = 3; + while (i--) + if ((c = (*fg)()) >= '0' && c <= '7') + wc = (wc << 3) + (c - '0'); + else { + (*fp)(c); + break; + } + break; + case 'U': + i = 8; + if (0) + /* FALLTHROUGH */ + case 'u': + i = 4; + if (0) + /* FALLTHROUGH */ + case 'x': + i = cstyle ? -1 : 2; + /* + * x: look for a hexadecimal number with up to + * two (C style: arbitrary) digits; convert + * to raw octet (C style: Unicode if >0xFF) + * u/U: look for a hexadecimal number with up to + * four (U: eight) digits; convert to Unicode + */ + wc = 0; + while (i--) { + wc <<= 4; + if ((c = (*fg)()) >= '0' && c <= '9') + wc += c - '0'; + else if (c >= 'A' && c <= 'F') + wc += c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + wc += c - 'a' + 10; + else { + wc >>= 4; + (*fp)(c); + break; + } + } + if ((cstyle && wc > 0xFF) || fc != 'x') + /* Unicode marker */ + wc += 0x100; + break; + case '\'': + if (!cstyle) + goto unknown_escape; + wc = '\''; + break; + case '\\': + wc = '\\'; + break; + default: + unknown_escape: + (*fp)(fc); + return (-1); + } + + return (wc); +} diff --git a/mksh/src/sh.h b/mksh/src/sh.h new file mode 100644 index 0000000..11588c9 --- /dev/null +++ b/mksh/src/sh.h @@ -0,0 +1,1752 @@ +/* $OpenBSD: sh.h,v 1.30 2010/01/04 18:07:11 deraadt Exp $ */ +/* $OpenBSD: shf.h,v 1.6 2005/12/11 18:53:51 deraadt Exp $ */ +/* $OpenBSD: table.h,v 1.7 2005/12/11 20:31:21 otto Exp $ */ +/* $OpenBSD: tree.h,v 1.10 2005/03/28 21:28:22 deraadt Exp $ */ +/* $OpenBSD: expand.h,v 1.6 2005/03/30 17:16:37 deraadt Exp $ */ +/* $OpenBSD: lex.h,v 1.11 2006/05/29 18:22:24 otto Exp $ */ +/* $OpenBSD: proto.h,v 1.33 2010/05/19 17:36:08 jasper Exp $ */ +/* $OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $ */ +/* $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#ifdef __dietlibc__ +/* XXX imake style */ +#define _BSD_SOURCE /* live, BSD, live! */ +#endif + +#if HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#if HAVE_SYS_SYSMACROS_H +#include <sys/sysmacros.h> +#endif +#if HAVE_SYS_MKDEV_H +#include <sys/mkdev.h> +#endif +#if HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#if HAVE_LIBGEN_H +#include <libgen.h> +#endif +#if HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#include <limits.h> +#if HAVE_PATHS_H +#include <paths.h> +#endif +#include <pwd.h> +#include <setjmp.h> +#include <signal.h> +#include <stdarg.h> +#if HAVE_STDBOOL_H +#include <stdbool.h> +#endif +#include <stddef.h> +#if HAVE_STDINT_H +#include <stdint.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if HAVE_STRINGS_H +#include <strings.h> +#endif +#include <termios.h> +#include <time.h> +#if HAVE_ULIMIT_H +#include <ulimit.h> +#endif +#include <unistd.h> +#if HAVE_VALUES_H +#include <values.h> +#endif + +#undef __attribute__ +#if HAVE_ATTRIBUTE_BOUNDED +#define MKSH_A_BOUNDED(x,y,z) __attribute__((bounded (x, y, z))) +#else +#define MKSH_A_BOUNDED(x,y,z) /* nothing */ +#endif +#if HAVE_ATTRIBUTE_FORMAT +#define MKSH_A_FORMAT(x,y,z) __attribute__((format (x, y, z))) +#else +#define MKSH_A_FORMAT(x,y,z) /* nothing */ +#endif +#if HAVE_ATTRIBUTE_NONNULL +#define MKSH_A_NONNULL(a) __attribute__(a) +#else +#define MKSH_A_NONNULL(a) /* nothing */ +#endif +#if HAVE_ATTRIBUTE_NORETURN +#define MKSH_A_NORETURN __attribute__((noreturn)) +#else +#define MKSH_A_NORETURN /* nothing */ +#endif +#if HAVE_ATTRIBUTE_UNUSED +#define MKSH_A_UNUSED __attribute__((unused)) +#else +#define MKSH_A_UNUSED /* nothing */ +#endif +#if HAVE_ATTRIBUTE_USED +#define MKSH_A_USED __attribute__((used)) +#else +#define MKSH_A_USED /* nothing */ +#endif + +#if defined(MirBSD) && (MirBSD >= 0x09A1) && \ + defined(__ELF__) && defined(__GNUC__) && \ + !defined(__llvm__) && !defined(__NWCC__) +/* + * We got usable __IDSTRING __COPYRIGHT __RCSID __SCCSID macros + * which work for all cases; no need to redefine them using the + * "portable" macros from below when we might have the "better" + * gcc+ELF specific macros or other system dependent ones. + */ +#else +#undef __IDSTRING +#undef __IDSTRING_CONCAT +#undef __IDSTRING_EXPAND +#undef __COPYRIGHT +#undef __RCSID +#undef __SCCSID +#define __IDSTRING_CONCAT(l,p) __LINTED__ ## l ## _ ## p +#define __IDSTRING_EXPAND(l,p) __IDSTRING_CONCAT(l,p) +#define __IDSTRING(prefix, string) \ + static const char __IDSTRING_EXPAND(__LINE__,prefix) [] \ + MKSH_A_USED = "@(""#)" #prefix ": " string +#define __COPYRIGHT(x) __IDSTRING(copyright,x) +#define __RCSID(x) __IDSTRING(rcsid,x) +#define __SCCSID(x) __IDSTRING(sccsid,x) +#endif + +#ifdef EXTERN +__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.405 2010/08/24 15:19:54 tg Exp $"); +#endif +#define MKSH_VERSION "R39 2010/08/24" + +#ifndef MKSH_INCLUDES_ONLY + +/* extra types */ + +#if !HAVE_GETRUSAGE +#undef rusage +#undef RUSAGE_SELF +#undef RUSAGE_CHILDREN +#define rusage mksh_rusage +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN -1 + +struct rusage { + struct timeval ru_utime; + struct timeval ru_stime; +}; +#endif + +#if !HAVE_RLIM_T +typedef long rlim_t; +#endif + +#if !HAVE_SIG_T +#undef sig_t +typedef void (*sig_t)(int); +#endif + +#if !HAVE_STDBOOL_H +/* kludge, but enough for mksh */ +typedef int bool; +#define false 0 +#define true 1 +#endif + +#if !HAVE_CAN_INTTYPES +#if !HAVE_CAN_UCBINTS +typedef signed int int32_t; +typedef unsigned int uint32_t; +#else +typedef u_int32_t uint32_t; +#endif +#endif + +#if !HAVE_CAN_INT8TYPE +#if !HAVE_CAN_UCBINT8 +typedef unsigned char uint8_t; +#else +typedef u_int8_t uint8_t; +#endif +#endif + +/* extra macros */ + +#ifndef timerclear +#define timerclear(tvp) \ + do { \ + (tvp)->tv_sec = (tvp)->tv_usec = 0; \ + } while (/* CONSTCOND */ 0) +#endif +#ifndef timeradd +#define timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (/* CONSTCOND */ 0) +#endif +#ifndef timersub +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (/* CONSTCOND */ 0) +#endif + +#define ksh_isdigit(c) (((c) >= '0') && ((c) <= '9')) +#define ksh_islower(c) (((c) >= 'a') && ((c) <= 'z')) +#define ksh_isupper(c) (((c) >= 'A') && ((c) <= 'Z')) +#define ksh_tolower(c) (((c) >= 'A') && ((c) <= 'Z') ? (c) - 'A' + 'a' : (c)) +#define ksh_toupper(c) (((c) >= 'a') && ((c) <= 'z') ? (c) - 'a' + 'A' : (c)) +#define ksh_isdash(s) (((s) != NULL) && ((s)[0] == '-') && ((s)[1] == '\0')) +#define ksh_isspace(c) ((((c) >= 0x09) && ((c) <= 0x0D)) || ((c) == 0x20)) + +#ifdef NO_PATH_MAX +#undef PATH_MAX +#else +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif +#endif +#ifndef SIZE_MAX +#ifdef SIZE_T_MAX +#define SIZE_MAX SIZE_T_MAX +#else +#define SIZE_MAX ((size_t)-1) +#endif +#endif +#ifndef S_ISLNK +#define S_ISLNK(m) ((m & 0170000) == 0120000) +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(m) ((m & 0170000) == 0140000) +#endif +#ifndef DEFFILEMODE +#define DEFFILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) +#endif + +#if !defined(MAP_FAILED) +/* XXX imake style */ +# if defined(__linux) +#define MAP_FAILED ((void *)-1) +# elif defined(__bsdi__) || defined(__osf__) || defined(__ultrix) +#define MAP_FAILED ((caddr_t)-1) +# endif +#endif + +#ifndef NSIG +#if defined(_NSIG) +#define NSIG _NSIG +#elif defined(SIGMAX) +#define NSIG (SIGMAX+1) +#endif +#endif + +#undef BAD /* AIX defines that somewhere */ + +/* OS-dependent additions (functions, variables, by OS) */ + +#if !HAVE_FLOCK_DECL +extern int flock(int, int); +#endif + +#if !HAVE_GETRUSAGE +extern int getrusage(int, struct rusage *); +#endif + +#if !HAVE_REVOKE_DECL +extern int revoke(const char *); +#endif + +#if !HAVE_SETMODE +mode_t getmode(const void *, mode_t); +void *setmode(const char *); +#endif + +#ifdef __ultrix +/* XXX imake style */ +int strcasecmp(const char *, const char *); +#endif + +#if !HAVE_STRCASESTR +const char *stristr(const char *, const char *); +#endif + +#if !HAVE_STRLCPY +size_t strlcpy(char *, const char *, size_t); +#endif + +#if !HAVE_SYS_SIGLIST_DECL +extern const char *const sys_siglist[]; +#endif + +#ifdef __INTERIX +/* XXX imake style */ +#define makedev mkdev +extern int __cdecl seteuid(uid_t); +extern int __cdecl setegid(gid_t); +#endif + +/* remove redundances */ + +#if defined(MirBSD) && (MirBSD >= 0x08A8) +#define MKSH_mirbsd_wcwidth +#define utf_wcwidth(i) wcwidth((__WCHAR_TYPE__)i) +extern int wcwidth(__WCHAR_TYPE__); +#endif + + +/* some useful #defines */ +#ifdef EXTERN +# define I__(i) = i +#else +# define I__(i) +# define EXTERN extern +# define EXTERN_DEFINED +#endif + +#define NELEM(a) (sizeof(a) / sizeof((a)[0])) +#define BIT(i) (1 << (i)) /* define bit in flag */ + +/* Table flag type - needs > 16 and < 32 bits */ +typedef int32_t Tflag; + +/* arithmetics types */ +typedef int32_t mksh_ari_t; +typedef uint32_t mksh_uari_t; + +/* these shall be smaller than 100 */ +#ifdef MKSH_CONSERVATIVE_FDS +#define NUFILE 32 /* Number of user-accessible files */ +#define FDBASE 10 /* First file usable by Shell */ +#else +#define NUFILE 56 /* Number of user-accessible files */ +#define FDBASE 24 /* First file usable by Shell */ +#endif + +/* Make MAGIC a char that might be printed to make bugs more obvious, but + * not a char that is used often. Also, can't use the high bit as it causes + * portability problems (calling strchr(x, 0x80|'x') is error prone). + */ +#define MAGIC (7) /* prefix for *?[!{,} during expand */ +#define ISMAGIC(c) ((unsigned char)(c) == MAGIC) +#define NOT '!' /* might use ^ (ie, [!...] vs [^..]) */ + +#define LINE 4096 /* input line size */ + +EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */ +EXTERN const char initvsn[] I__("KSH_VERSION=@(#)MIRBSD KSH " MKSH_VERSION); +#define KSH_VERSION (initvsn + /* "KSH_VERSION=@(#)" */ 16) + +EXTERN const char digits_uc[] I__("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"); +EXTERN const char digits_lc[] I__("0123456789abcdefghijklmnopqrstuvwxyz"); + +/* + * Evil hack for const correctness due to API brokenness + */ +union mksh_cchack { + char *rw; + const char *ro; +}; +union mksh_ccphack { + char **rw; + const char **ro; +}; + +/* for const debugging */ +#if defined(DEBUG) && defined(__GNUC__) && !defined(__ICC) && \ + !defined(__INTEL_COMPILER) && !defined(__SUNPRO_C) +char *ucstrchr(char *, int); +char *ucstrstr(char *, const char *); +#undef strchr +#define strchr ucstrchr +#define strstr ucstrstr +#define cstrchr(s,c) ({ \ + union mksh_cchack in, out; \ + \ + in.ro = (s); \ + out.rw = ucstrchr(in.rw, (c)); \ + (out.ro); \ +}) +#define cstrstr(b,l) ({ \ + union mksh_cchack in, out; \ + \ + in.ro = (b); \ + out.rw = ucstrstr(in.rw, (l)); \ + (out.ro); \ +}) +#define vstrchr(s,c) (cstrchr((s), (c)) != NULL) +#define vstrstr(b,l) (cstrstr((b), (l)) != NULL) +#define mkssert(e) ((e) ? (void)0 : exit(255)) +#else /* !DEBUG, !gcc */ +#define cstrchr(s,c) ((const char *)strchr((s), (c))) +#define cstrstr(s,c) ((const char *)strstr((s), (c))) +#define vstrchr(s,c) (strchr((s), (c)) != NULL) +#define vstrstr(b,l) (strstr((b), (l)) != NULL) +#define mkssert(e) ((void)0) +#endif + +/* use this ipv strchr(s, 0) but no side effects in s! */ +#define strnul(s) ((s) + strlen(s)) + +#define utf_ptradjx(src, dst) do { \ + (dst) = (src) + utf_ptradj(src); \ +} while (/* CONSTCOND */ 0) + +#ifdef MKSH_SMALL +#define strdupx(d, s, ap) do { \ + (d) = strdup_((s), (ap)); \ +} while (/* CONSTCOND */ 0) +#define strndupx(d, s, n, ap) do { \ + (d) = strndup_((s), (n), (ap)); \ +} while (/* CONSTCOND */ 0) +#else +/* be careful to evaluate arguments only once! */ +#define strdupx(d, s, ap) do { \ + const char *strdup_src = (s); \ + char *strdup_dst = NULL; \ + \ + if (strdup_src != NULL) { \ + size_t strdup_len = strlen(strdup_src) + 1; \ + strdup_dst = alloc(strdup_len, (ap)); \ + memcpy(strdup_dst, strdup_src, strdup_len); \ + } \ + (d) = strdup_dst; \ +} while (/* CONSTCOND */ 0) +#define strndupx(d, s, n, ap) do { \ + const char *strdup_src = (s); \ + char *strdup_dst = NULL; \ + \ + if (strdup_src != NULL) { \ + size_t strndup_len = (n); \ + strdup_dst = alloc(strndup_len + 1, (ap)); \ + memcpy(strdup_dst, strdup_src, strndup_len); \ + strdup_dst[strndup_len] = '\0'; \ + } \ + (d) = strdup_dst; \ +} while (/* CONSTCOND */ 0) +#endif + +#if HAVE_STRCASESTR +#define stristr(b,l) ((const char *)strcasestr((b), (l))) +#endif + +#ifdef MKSH_SMALL +#ifndef MKSH_CONSERVATIVE_FDS +#define MKSH_CONSERVATIVE_FDS /* defined */ +#endif +#ifndef MKSH_NOPWNAM +#define MKSH_NOPWNAM /* defined */ +#endif +#ifndef MKSH_S_NOVI +#define MKSH_S_NOVI 1 +#endif +#endif + +#ifndef MKSH_S_NOVI +#define MKSH_S_NOVI 0 +#endif + +/* + * simple grouping allocator + */ + +/* 1. internal structure */ +struct lalloc { + struct lalloc *next; +}; + +/* 2. sizes */ +#define ALLOC_ITEM struct lalloc +#define ALLOC_SIZE (sizeof(ALLOC_ITEM)) + +/* 3. group structure (only the same for lalloc.c) */ +typedef struct lalloc Area; + + +EXTERN Area aperm; /* permanent object space */ +#define APERM &aperm +#define ATEMP &e->area + +/* + * flags (the order of these enums MUST match the order in misc.c(options[])) + */ +enum sh_flag { +#define SHFLAGS_ENUMS +#include "sh_flags.h" + FNFLAGS /* (place holder: how many flags are there) */ +}; + +#define Flag(f) (kshstate_v.shell_flags_[(int)(f)]) +#define UTFMODE Flag(FUNICODE) + +/* + * parsing & execution environment + */ +extern struct env { + ALLOC_ITEM __alloc_i; /* internal, do not touch */ + Area area; /* temporary allocation area */ + struct env *oenv; /* link to previous environment */ + struct block *loc; /* local variables and functions */ + short *savefd; /* original redirected fds */ + struct temp *temps; /* temp files */ + sigjmp_buf jbuf; /* long jump back to env creator */ + short type; /* environment type - see below */ + short flags; /* EF_* */ +} *e; + +/* struct env.type values */ +#define E_NONE 0 /* dummy environment */ +#define E_PARSE 1 /* parsing command # */ +#define E_FUNC 2 /* executing function # */ +#define E_INCL 3 /* including a file via . # */ +#define E_EXEC 4 /* executing command tree */ +#define E_LOOP 5 /* executing for/while # */ +#define E_ERRH 6 /* general error handler # */ +/* # indicates env has valid jbuf (see unwind()) */ + +/* struct env.flag values */ +#define EF_FUNC_PARSE BIT(0) /* function being parsed */ +#define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */ +#define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */ + +/* Do breaks/continues stop at env type e? */ +#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE \ + || (t) == E_FUNC || (t) == E_INCL) +/* Do returns stop at env type e? */ +#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL) + +/* values for siglongjmp(e->jbuf, 0) */ +#define LRETURN 1 /* return statement */ +#define LEXIT 2 /* exit statement */ +#define LERROR 3 /* errorf() called */ +#define LLEAVE 4 /* untrappable exit/error */ +#define LINTR 5 /* ^C noticed */ +#define LBREAK 6 /* break statement */ +#define LCONTIN 7 /* continue statement */ +#define LSHELL 8 /* return to interactive shell() */ +#define LAEXPR 9 /* error in arithmetic expression */ + +/* + * some kind of global shell state, for change_random() mostly + */ + +EXTERN struct mksh_kshstate_v { + /* for change_random */ + struct timeval cr_tv; /* timestamp */ + const void *cr_dp; /* argument address */ + size_t cr_dsz; /* argument length */ + uint32_t lcg_state_; /* previous LCG state */ + /* global state */ + pid_t procpid_; /* PID of executing process */ + int exstat_; /* exit status */ + int subst_exstat_; /* exit status of last $(..)/`..` */ + struct env env_; /* top-level parsing & execution env. */ + uint8_t shell_flags_[FNFLAGS]; +} kshstate_v; +EXTERN struct mksh_kshstate_f { + const char *kshname_; /* $0 */ + pid_t kshpid_; /* $$, shell PID */ + pid_t kshpgrp_; /* process group of shell */ + uid_t ksheuid_; /* effective UID of shell */ + pid_t kshppid_; /* PID of parent of shell */ + uint32_t h; /* some kind of hash */ +} kshstate_f; +#define kshname kshstate_f.kshname_ +#define kshpid kshstate_f.kshpid_ +#define procpid kshstate_v.procpid_ +#define kshpgrp kshstate_f.kshpgrp_ +#define ksheuid kshstate_f.ksheuid_ +#define kshppid kshstate_f.kshppid_ +#define exstat kshstate_v.exstat_ +#define subst_exstat kshstate_v.subst_exstat_ + +/* evil hack: return hash(kshstate_f concat (kshstate_f'.h:=hash(arg))) */ +uint32_t evilhash(const char *); + + +/* option processing */ +#define OF_CMDLINE 0x01 /* command line */ +#define OF_SET 0x02 /* set builtin */ +#define OF_SPECIAL 0x04 /* a special variable changing */ +#define OF_INTERNAL 0x08 /* set internally by shell */ +#define OF_FIRSTTIME 0x10 /* as early as possible, once */ +#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL) + +struct shoption { + const char *name; /* long name of option */ + char c; /* character flag (if any) */ + unsigned char flags; /* OF_* */ +}; +extern const struct shoption options[]; + +/* null value for variable; comparision pointer for unset */ +EXTERN char null[] I__(""); +/* helpers for string pooling */ +#define T_synerr "syntax error" +EXTERN const char r_fc_e_[] I__("r=fc -e -"); +#define fc_e_ (r_fc_e_ + 2) /* "fc -e -" */ +#define fc_e_n 7 /* strlen(fc_e_) */ +EXTERN const char T_local_typeset[] I__("local=typeset"); +#define T__typeset (T_local_typeset + 5) /* "=typeset" */ +#define T_typeset (T_local_typeset + 6) /* "typeset" */ + +enum temp_type { + TT_HEREDOC_EXP, /* expanded heredoc */ + TT_HIST_EDIT /* temp file used for history editing (fc -e) */ +}; +typedef enum temp_type Temp_type; +/* temp/heredoc files. The file is removed when the struct is freed. */ +struct temp { + struct temp *next; + struct shf *shf; + char *name; + int pid; /* pid of process parsed here-doc */ + Temp_type type; +}; + +/* + * stdio and our IO routines + */ + +#define shl_spare (&shf_iob[0]) /* for c_read()/c_print() */ +#define shl_stdout (&shf_iob[1]) +#define shl_out (&shf_iob[2]) +EXTERN int shl_stdout_ok; + +/* + * trap handlers + */ +typedef struct trap { + const char *name; /* short name */ + const char *mess; /* descriptive name */ + char *trap; /* trap command */ + sig_t cursig; /* current handler (valid if TF_ORIG_* set) */ + sig_t shtrap; /* shell signal handler */ + int signal; /* signal number */ + int flags; /* TF_* */ + volatile sig_atomic_t set; /* trap pending */ +} Trap; + +/* values for Trap.flags */ +#define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */ +#define TF_USER_SET BIT(1) /* user has (tried to) set trap */ +#define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */ +#define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */ +#define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */ +#define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */ +#define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */ +#define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */ +#define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */ +#define TF_FATAL BIT(9) /* causes termination if not trapped */ + +/* values for setsig()/setexecsig() flags argument */ +#define SS_RESTORE_MASK 0x3 /* how to restore a signal before an exec() */ +#define SS_RESTORE_CURR 0 /* leave current handler in place */ +#define SS_RESTORE_ORIG 1 /* restore original handler */ +#define SS_RESTORE_DFL 2 /* restore to SIG_DFL */ +#define SS_RESTORE_IGN 3 /* restore to SIG_IGN */ +#define SS_FORCE BIT(3) /* set signal even if original signal ignored */ +#define SS_USER BIT(4) /* user is doing the set (ie, trap command) */ +#define SS_SHTRAP BIT(5) /* trap for internal use (ALRM, CHLD, WINCH) */ + +#define SIGEXIT_ 0 /* for trap EXIT */ +#define SIGERR_ NSIG /* for trap ERR */ + +EXTERN volatile sig_atomic_t trap; /* traps pending? */ +EXTERN volatile sig_atomic_t intrsig; /* pending trap interrupts command */ +EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */ +extern Trap sigtraps[NSIG+1]; + +/* got_winch = 1 when we need to re-adjust the window size */ +#ifdef SIGWINCH +EXTERN volatile sig_atomic_t got_winch I__(1); +#else +#define got_winch true +#endif + +/* + * TMOUT support + */ +/* values for ksh_tmout_state */ +enum tmout_enum { + TMOUT_EXECUTING = 0, /* executing commands */ + TMOUT_READING, /* waiting for input */ + TMOUT_LEAVING /* have timed out */ +}; +EXTERN unsigned int ksh_tmout; +EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING); + +/* For "You have stopped jobs" message */ +EXTERN int really_exit; + +/* + * fast character classes + */ +#define C_ALPHA BIT(0) /* a-z_A-Z */ +#define C_DIGIT BIT(1) /* 0-9 */ +#define C_LEX1 BIT(2) /* \t \n\0|&;<>() */ +#define C_VAR1 BIT(3) /* *@#!$-? */ +#define C_IFSWS BIT(4) /* \t \n (IFS white space) */ +#define C_SUBOP1 BIT(5) /* "=-+?" */ +#define C_QUOTE BIT(6) /* \t\n "#$&'()*;<=>?[\]`| (needing quoting) */ +#define C_IFS BIT(7) /* $IFS */ +#define C_SUBOP2 BIT(8) /* "#%" (magic, see below) */ + +extern unsigned char chtypes[]; + +#define ctype(c, t) !!( ((t) == C_SUBOP2) ? \ + (((c) == '#' || (c) == '%') ? 1 : 0) : \ + (chtypes[(unsigned char)(c)]&(t)) ) +#define ksh_isalphx(c) ctype((c), C_ALPHA) +#define ksh_isalnux(c) ctype((c), C_ALPHA | C_DIGIT) + +EXTERN int ifs0 I__(' '); /* for "$*" */ + +/* Argument parsing for built-in commands and getopts command */ + +/* Values for Getopt.flags */ +#define GF_ERROR BIT(0) /* call errorf() if there is an error */ +#define GF_PLUSOPT BIT(1) /* allow +c as an option */ +#define GF_NONAME BIT(2) /* don't print argv[0] in errors */ + +/* Values for Getopt.info */ +#define GI_MINUS BIT(0) /* an option started with -... */ +#define GI_PLUS BIT(1) /* an option started with +... */ +#define GI_MINUSMINUS BIT(2) /* arguments were ended with -- */ + +typedef struct { + const char *optarg; + int optind; + int uoptind;/* what user sees in $OPTIND */ + int flags; /* see GF_* */ + int info; /* see GI_* */ + unsigned int p; /* 0 or index into argv[optind - 1] */ + char buf[2]; /* for bad option OPTARG value */ +} Getopt; + +EXTERN Getopt builtin_opt; /* for shell builtin commands */ +EXTERN Getopt user_opt; /* parsing state for getopts builtin command */ + +/* This for co-processes */ + +typedef int32_t Coproc_id; /* something that won't (realisticly) wrap */ +struct coproc { + void *job; /* 0 or job of co-process using input pipe */ + int read; /* pipe from co-process's stdout */ + int readw; /* other side of read (saved temporarily) */ + int write; /* pipe to co-process's stdin */ + int njobs; /* number of live jobs using output pipe */ + Coproc_id id; /* id of current output pipe */ +}; +EXTERN struct coproc coproc; + +/* Used in jobs.c and by coprocess stuff in exec.c */ +EXTERN sigset_t sm_default, sm_sigchld; + +/* name of called builtin function (used by error functions) */ +EXTERN const char *builtin_argv0; +EXTERN Tflag builtin_flag; /* flags of called builtin (SPEC_BI, etc.) */ + +/* current working directory, and size of memory allocated for same */ +EXTERN char *current_wd; +EXTERN size_t current_wd_size; + +/* Minimum required space to work with on a line - if the prompt leaves less + * space than this on a line, the prompt is truncated. + */ +#define MIN_EDIT_SPACE 7 +/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line + */ +#define MIN_COLS (2 + MIN_EDIT_SPACE + 3) +#define MIN_LINS 3 +EXTERN mksh_ari_t x_cols I__(80); /* tty columns */ +EXTERN mksh_ari_t x_lins I__(-1); /* tty lines */ + +/* These to avoid bracket matching problems */ +#define OPAREN '(' +#define CPAREN ')' +#define OBRACK '[' +#define CBRACK ']' +#define OBRACE '{' +#define CBRACE '}' + +/* Determine the location of the system (common) profile */ +#define KSH_SYSTEM_PROFILE "/etc/profile" + +/* Used by v_evaluate() and setstr() to control action when error occurs */ +#define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */ +#define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */ + +/* + * Shell file I/O routines + */ + +#define SHF_BSIZE 512 + +#define shf_fileno(shf) ((shf)->fd) +#define shf_setfileno(shf,nfd) ((shf)->fd = (nfd)) +#ifdef MKSH_SMALL +int shf_getc(struct shf *); +int shf_putc(int, struct shf *); +#else +#define shf_getc(shf) ((shf)->rnleft > 0 ? \ + (shf)->rnleft--, *(shf)->rp++ : \ + shf_getchar(shf)) +#define shf_putc(c, shf) ((shf)->wnleft == 0 ? \ + shf_putchar((c), (shf)) : \ + ((shf)->wnleft--, *(shf)->wp++ = (c))) +#endif +#define shf_eof(shf) ((shf)->flags & SHF_EOF) +#define shf_error(shf) ((shf)->flags & SHF_ERROR) +#define shf_errno(shf) ((shf)->errno_) +#define shf_clearerr(shf) ((shf)->flags &= ~(SHF_EOF | SHF_ERROR)) + +/* Flags passed to shf_*open() */ +#define SHF_RD 0x0001 +#define SHF_WR 0x0002 +#define SHF_RDWR (SHF_RD|SHF_WR) +#define SHF_ACCMODE 0x0003 /* mask */ +#define SHF_GETFL 0x0004 /* use fcntl() to figure RD/WR flags */ +#define SHF_UNBUF 0x0008 /* unbuffered I/O */ +#define SHF_CLEXEC 0x0010 /* set close on exec flag */ +#define SHF_MAPHI 0x0020 /* make fd > FDBASE (and close orig) + * (shf_open() only) */ +#define SHF_DYNAMIC 0x0040 /* string: increase buffer as needed */ +#define SHF_INTERRUPT 0x0080 /* EINTR in read/write causes error */ +/* Flags used internally */ +#define SHF_STRING 0x0100 /* a string, not a file */ +#define SHF_ALLOCS 0x0200 /* shf and shf->buf were alloc()ed */ +#define SHF_ALLOCB 0x0400 /* shf->buf was alloc()ed */ +#define SHF_ERROR 0x0800 /* read()/write() error */ +#define SHF_EOF 0x1000 /* read eof (sticky) */ +#define SHF_READING 0x2000 /* currently reading: rnleft,rp valid */ +#define SHF_WRITING 0x4000 /* currently writing: wnleft,wp valid */ + + +struct shf { + Area *areap; /* area shf/buf were allocated in */ + unsigned char *rp; /* read: current position in buffer */ + unsigned char *wp; /* write: current position in buffer */ + unsigned char *buf; /* buffer */ + int flags; /* see SHF_* */ + int rbsize; /* size of buffer (1 if SHF_UNBUF) */ + int rnleft; /* read: how much data left in buffer */ + int wbsize; /* size of buffer (0 if SHF_UNBUF) */ + int wnleft; /* write: how much space left in buffer */ + int fd; /* file descriptor */ + int errno_; /* saved value of errno after error */ + int bsize; /* actual size of buf */ +}; + +extern struct shf shf_iob[]; + +struct table { + Area *areap; /* area to allocate entries */ + struct tbl **tbls; /* hashed table items */ + short size, nfree; /* hash size (always 2^^n), free entries */ +}; + +struct tbl { /* table item */ + Area *areap; /* area to allocate from */ + union { + char *s; /* string */ + mksh_ari_t i; /* integer */ + mksh_uari_t u; /* unsigned integer */ + int (*f)(const char **);/* int function */ + struct op *t; /* "function" tree */ + } val; /* value */ + union { + struct tbl *array; /* array values */ + const char *fpath; /* temporary path to undef function */ + } u; + union { + int field; /* field with for -L/-R/-Z */ + int errno_; /* CEXEC/CTALIAS */ + } u2; + int type; /* command type (see below), base (if INTEGER), + * or offset from val.s of value (if EXPORT) */ + Tflag flag; /* flags */ + union { + uint32_t hval; /* hash(name) */ + uint32_t index; /* index for an array */ + } ua; + char name[4]; /* name -- variable length */ +}; + +/* common flag bits */ +#define ALLOC BIT(0) /* val.s has been allocated */ +#define DEFINED BIT(1) /* is defined in block */ +#define ISSET BIT(2) /* has value, vp->val.[si] */ +#define EXPORT BIT(3) /* exported variable/function */ +#define TRACE BIT(4) /* var: user flagged, func: execution tracing */ +/* (start non-common flags at 8) */ +/* flag bits used for variables */ +#define SPECIAL BIT(8) /* PATH, IFS, SECONDS, etc */ +#define INTEGER BIT(9) /* val.i contains integer value */ +#define RDONLY BIT(10) /* read-only variable */ +#define LOCAL BIT(11) /* for local typeset() */ +#define ARRAY BIT(13) /* array */ +#define LJUST BIT(14) /* left justify */ +#define RJUST BIT(15) /* right justify */ +#define ZEROFIL BIT(16) /* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */ +#define LCASEV BIT(17) /* convert to lower case */ +#define UCASEV_AL BIT(18) /* convert to upper case / autoload function */ +#define INT_U BIT(19) /* unsigned integer */ +#define INT_L BIT(20) /* long integer (no-op) */ +#define IMPORT BIT(21) /* flag to typeset(): no arrays, must have = */ +#define LOCAL_COPY BIT(22) /* with LOCAL - copy attrs from existing var */ +#define EXPRINEVAL BIT(23) /* contents currently being evaluated */ +#define EXPRLVALUE BIT(24) /* useable as lvalue (temp flag) */ +#define AINDEX BIT(25) /* array index >0 = ua.index filled in */ +#define ASSOC BIT(26) /* ARRAY ? associative : reference */ +/* flag bits used for taliases/builtins/aliases/keywords/functions */ +#define KEEPASN BIT(8) /* keep command assignments (eg, var=x cmd) */ +#define FINUSE BIT(9) /* function being executed */ +#define FDELETE BIT(10) /* function deleted while it was executing */ +#define FKSH BIT(11) /* function defined with function x (vs x()) */ +#define SPEC_BI BIT(12) /* a POSIX special builtin */ +#define REG_BI BIT(13) /* a POSIX regular builtin */ +/* Attributes that can be set by the user (used to decide if an unset param + * should be repoted by set/typeset). Does not include ARRAY or LOCAL. + */ +#define USERATTRIB (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL|\ + LCASEV|UCASEV_AL|INT_U|INT_L) + +#define arrayindex(vp) ((unsigned long)((vp)->flag & AINDEX ? \ + (vp)->ua.index : 0)) + +/* command types */ +#define CNONE 0 /* undefined */ +#define CSHELL 1 /* built-in */ +#define CFUNC 2 /* function */ +#define CEXEC 4 /* executable command */ +#define CALIAS 5 /* alias */ +#define CKEYWD 6 /* keyword */ +#define CTALIAS 7 /* tracked alias */ + +/* Flags for findcom()/comexec() */ +#define FC_SPECBI BIT(0) /* special builtin */ +#define FC_FUNC BIT(1) /* function builtin */ +#define FC_REGBI BIT(2) /* regular builtin */ +#define FC_UNREGBI BIT(3) /* un-regular builtin (!special,!regular) */ +#define FC_BI (FC_SPECBI|FC_REGBI|FC_UNREGBI) +#define FC_PATH BIT(4) /* do path search */ +#define FC_DEFPATH BIT(5) /* use default path in path search */ + + +#define AF_ARGV_ALLOC 0x1 /* argv[] array allocated */ +#define AF_ARGS_ALLOCED 0x2 /* argument strings allocated */ +#define AI_ARGV(a, i) ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip]) +#define AI_ARGC(a) ((a).argc_ - (a).skip) + +/* Argument info. Used for $#, $* for shell, functions, includes, etc. */ +struct arg_info { + const char **argv; + int flags; /* AF_* */ + int argc_; + int skip; /* first arg is argv[0], second is argv[1 + skip] */ +}; + +/* + * activation record for function blocks + */ +struct block { + Area area; /* area to allocate things */ + const char **argv; + char *error; /* error handler */ + char *exit; /* exit handler */ + struct block *next; /* enclosing block */ + struct table vars; /* local variables */ + struct table funs; /* local functions */ + Getopt getopts_state; + int argc; + int flags; /* see BF_* */ +}; + +/* Values for struct block.flags */ +#define BF_DOGETOPTS BIT(0) /* save/restore getopts state */ + +/* + * Used by ktwalk() and ktnext() routines. + */ +struct tstate { + struct tbl **next; + ssize_t left; +}; + +EXTERN struct table taliases; /* tracked aliases */ +EXTERN struct table builtins; /* built-in commands */ +EXTERN struct table aliases; /* aliases */ +EXTERN struct table keywords; /* keywords */ +#ifndef MKSH_NOPWNAM +EXTERN struct table homedirs; /* homedir() cache */ +#endif + +struct builtin { + const char *name; + int (*func)(const char **); +}; + +extern const struct builtin mkshbuiltins[]; + +/* values for set_prompt() */ +#define PS1 0 /* command */ +#define PS2 1 /* command continuation */ + +EXTERN char *path; /* copy of either PATH or def_path */ +EXTERN const char *def_path; /* path to use if PATH not set */ +EXTERN char *tmpdir; /* TMPDIR value */ +EXTERN const char *prompt; +EXTERN int cur_prompt; /* PS1 or PS2 */ +EXTERN int current_lineno; /* LINENO value */ + +#define NOBLOCK ((struct op *)NULL) +#define NOWORD ((char *)NULL) +#define NOWORDS ((char **)NULL) + +/* + * Description of a command or an operation on commands. + */ +struct op { + const char **args; /* arguments to a command */ + char **vars; /* variable assignments */ + struct ioword **ioact; /* IO actions (eg, < > >>) */ + struct op *left, *right; /* descendents */ + char *str; /* word for case; identifier for for, + * select, and functions; + * path to execute for TEXEC; + * time hook for TCOM. + */ + int lineno; /* TCOM/TFUNC: LINENO for this */ + short type; /* operation type, see below */ + union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ + short evalflags; /* TCOM: arg expansion eval() flags */ + short ksh_func; /* TFUNC: function x (vs x()) */ + } u; +}; + +/* Tree.type values */ +#define TEOF 0 +#define TCOM 1 /* command */ +#define TPAREN 2 /* (c-list) */ +#define TPIPE 3 /* a | b */ +#define TLIST 4 /* a ; b */ +#define TOR 5 /* || */ +#define TAND 6 /* && */ +#define TBANG 7 /* ! */ +#define TDBRACKET 8 /* [[ .. ]] */ +#define TFOR 9 +#define TSELECT 10 +#define TCASE 11 +#define TIF 12 +#define TWHILE 13 +#define TUNTIL 14 +#define TELIF 15 +#define TPAT 16 /* pattern in case */ +#define TBRACE 17 /* {c-list} */ +#define TASYNC 18 /* c & */ +#define TFUNCT 19 /* function name { command; } */ +#define TTIME 20 /* time pipeline */ +#define TEXEC 21 /* fork/exec eval'd TCOM */ +#define TCOPROC 22 /* coprocess |& */ + +/* + * prefix codes for words in command tree + */ +#define EOS 0 /* end of string */ +#define CHAR 1 /* unquoted character */ +#define QCHAR 2 /* quoted character */ +#define COMSUB 3 /* $() substitution (0 terminated) */ +#define EXPRSUB 4 /* $(()) substitution (0 terminated) */ +#define OQUOTE 5 /* opening " or ' */ +#define CQUOTE 6 /* closing " or ' */ +#define OSUBST 7 /* opening ${ subst (followed by { or X) */ +#define CSUBST 8 /* closing } of above (followed by } or X) */ +#define OPAT 9 /* open pattern: *(, @(, etc. */ +#define SPAT 10 /* separate pattern: | */ +#define CPAT 11 /* close pattern: ) */ +#define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */ + +/* + * IO redirection + */ +struct ioword { + int unit; /* unit affected */ + int flag; /* action (below) */ + char *name; /* file name (unused if heredoc) */ + char *delim; /* delimiter for <<,<<- */ + char *heredoc;/* content of heredoc */ +}; + +/* ioword.flag - type of redirection */ +#define IOTYPE 0xF /* type: bits 0:3 */ +#define IOREAD 0x1 /* < */ +#define IOWRITE 0x2 /* > */ +#define IORDWR 0x3 /* <>: todo */ +#define IOHERE 0x4 /* << (here file) */ +#define IOCAT 0x5 /* >> */ +#define IODUP 0x6 /* <&/>& */ +#define IOEVAL BIT(4) /* expand in << */ +#define IOSKIP BIT(5) /* <<-, skip ^\t* */ +#define IOCLOB BIT(6) /* >|, override -o noclobber */ +#define IORDUP BIT(7) /* x<&y (as opposed to x>&y) */ +#define IONAMEXP BIT(8) /* name has been expanded */ +#define IOBASH BIT(9) /* &> etc. */ + +/* execute/exchild flags */ +#define XEXEC BIT(0) /* execute without forking */ +#define XFORK BIT(1) /* fork before executing */ +#define XBGND BIT(2) /* command & */ +#define XPIPEI BIT(3) /* input is pipe */ +#define XPIPEO BIT(4) /* output is pipe */ +#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */ +#define XXCOM BIT(5) /* `...` command */ +#define XPCLOSE BIT(6) /* exchild: close close_fd in parent */ +#define XCCLOSE BIT(7) /* exchild: close close_fd in child */ +#define XERROK BIT(8) /* non-zero exit ok (for set -e) */ +#define XCOPROC BIT(9) /* starting a co-process */ +#define XTIME BIT(10) /* timing TCOM command */ + +/* + * flags to control expansion of words (assumed by t->evalflags to fit + * in a short) + */ +#define DOBLANK BIT(0) /* perform blank interpretation */ +#define DOGLOB BIT(1) /* expand [?* */ +#define DOPAT BIT(2) /* quote *?[ */ +#define DOTILDE BIT(3) /* normal ~ expansion (first char) */ +#define DONTRUNCOMMAND BIT(4) /* do not run $(command) things */ +#define DOASNTILDE BIT(5) /* assignment ~ expansion (after =, :) */ +#define DOBRACE_ BIT(6) /* used by expand(): do brace expansion */ +#define DOMAGIC_ BIT(7) /* used by expand(): string contains MAGIC */ +#define DOTEMP_ BIT(8) /* ditto : in word part of ${..[%#=?]..} */ +#define DOVACHECK BIT(9) /* var assign check (for typeset, set, etc) */ +#define DOMARKDIRS BIT(10) /* force markdirs behaviour */ + +/* + * The arguments of [[ .. ]] expressions are kept in t->args[] and flags + * indicating how the arguments have been munged are kept in t->vars[]. + * The contents of t->vars[] are stuffed strings (so they can be treated + * like all other t->vars[]) in which the second character is the one that + * is examined. The DB_* defines are the values for these second characters. + */ +#define DB_NORM 1 /* normal argument */ +#define DB_OR 2 /* || -> -o conversion */ +#define DB_AND 3 /* && -> -a conversion */ +#define DB_BE 4 /* an inserted -BE */ +#define DB_PAT 5 /* a pattern argument */ + +#define X_EXTRA 8 /* this many extra bytes in X string */ + +typedef struct XString { + char *end, *beg; /* end, begin of string */ + size_t len; /* length */ + Area *areap; /* area to allocate/free from */ +} XString; + +typedef char *XStringP; + +/* initialise expandable string */ +#define XinitN(xs, length, area) do { \ + (xs).len = (length); \ + (xs).areap = (area); \ + (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \ + (xs).end = (xs).beg + (xs).len; \ +} while (/* CONSTCOND */ 0) +#define Xinit(xs, xp, length, area) do { \ + XinitN((xs), (length), (area)); \ + (xp) = (xs).beg; \ +} while (/* CONSTCOND */ 0) + +/* stuff char into string */ +#define Xput(xs, xp, c) (*xp++ = (c)) + +/* check if there are at least n bytes left */ +#define XcheckN(xs, xp, n) do { \ + int more = ((xp) + (n)) - (xs).end; \ + if (more > 0) \ + (xp) = Xcheck_grow_(&(xs), (xp), more); \ +} while (/* CONSTCOND */ 0) + +/* check for overflow, expand string */ +#define Xcheck(xs, xp) XcheckN((xs), (xp), 1) + +/* free string */ +#define Xfree(xs, xp) afree((xs).beg, (xs).areap) + +/* close, return string */ +#define Xclose(xs, xp) aresize((xs).beg, (xp) - (xs).beg, (xs).areap) + +/* begin of string */ +#define Xstring(xs, xp) ((xs).beg) + +#define Xnleft(xs, xp) ((xs).end - (xp)) /* may be less than 0 */ +#define Xlength(xs, xp) ((xp) - (xs).beg) +#define Xsize(xs, xp) ((xs).end - (xs).beg) +#define Xsavepos(xs, xp) ((xp) - (xs).beg) +#define Xrestpos(xs, xp, n) ((xs).beg + (n)) + +char *Xcheck_grow_(XString *, const char *, unsigned int); + +/* + * expandable vector of generic pointers + */ + +typedef struct XPtrV { + void **cur; /* next avail pointer */ + void **beg, **end; /* begin, end of vector */ +} XPtrV; + +#define XPinit(x, n) do { \ + void **vp__; \ + vp__ = alloc((n) * sizeof(void *), ATEMP); \ + (x).cur = (x).beg = vp__; \ + (x).end = vp__ + (n); \ +} while (/* CONSTCOND */ 0) + +#define XPput(x, p) do { \ + if ((x).cur >= (x).end) { \ + size_t n = XPsize(x); \ + (x).beg = aresize((x).beg, \ + n * 2 * sizeof(void *), ATEMP); \ + (x).cur = (x).beg + n; \ + (x).end = (x).cur + n; \ + } \ + *(x).cur++ = (p); \ +} while (/* CONSTCOND */ 0) + +#define XPptrv(x) ((x).beg) +#define XPsize(x) ((x).cur - (x).beg) +#define XPclose(x) aresize((x).beg, XPsize(x) * sizeof(void *), ATEMP) +#define XPfree(x) afree((x).beg, ATEMP) + +#define IDENT 64 + +typedef struct source Source; +struct source { + const char *str; /* input pointer */ + const char *start; /* start of current buffer */ + union { + const char **strv; /* string [] */ + struct shf *shf; /* shell file */ + struct tbl *tblp; /* alias (SF_HASALIAS) */ + char *freeme; /* also for SREREAD */ + } u; + const char *file; /* input file name */ + int type; /* input type */ + int line; /* line number */ + int errline; /* line the error occurred on (0 if not set) */ + int flags; /* SF_* */ + Area *areap; + Source *next; /* stacked source */ + XString xs; /* input buffer */ + char ugbuf[2]; /* buffer for ungetsc() (SREREAD) and + * alias (SALIAS) */ +}; + +/* Source.type values */ +#define SEOF 0 /* input EOF */ +#define SFILE 1 /* file input */ +#define SSTDIN 2 /* read stdin */ +#define SSTRING 3 /* string */ +#define SWSTR 4 /* string without \n */ +#define SWORDS 5 /* string[] */ +#define SWORDSEP 6 /* string[] separator */ +#define SALIAS 7 /* alias expansion */ +#define SREREAD 8 /* read ahead to be re-scanned */ + +/* Source.flags values */ +#define SF_ECHO BIT(0) /* echo input to shlout */ +#define SF_ALIAS BIT(1) /* faking space at end of alias */ +#define SF_ALIASEND BIT(2) /* faking space at end of alias */ +#define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */ +#define SF_FIRST BIT(4) /* initial state (to ignore UTF-8 BOM) */ +#define SF_HASALIAS BIT(5) /* u.tblp valid (SALIAS, SEOF) */ + +typedef union { + int i; + char *cp; + char **wp; + struct op *o; + struct ioword *iop; +} YYSTYPE; + +/* If something is added here, add it to tokentab[] in syn.c as well */ +#define LWORD 256 +#define LOGAND 257 /* && */ +#define LOGOR 258 /* || */ +#define BREAK 259 /* ;; */ +#define IF 260 +#define THEN 261 +#define ELSE 262 +#define ELIF 263 +#define FI 264 +#define CASE 265 +#define ESAC 266 +#define FOR 267 +#define SELECT 268 +#define WHILE 269 +#define UNTIL 270 +#define DO 271 +#define DONE 272 +#define IN 273 +#define FUNCTION 274 +#define TIME 275 +#define REDIR 276 +#define MDPAREN 277 /* (( )) */ +#define BANG 278 /* ! */ +#define DBRACKET 279 /* [[ .. ]] */ +#define COPROC 280 /* |& */ +#define YYERRCODE 300 + +/* flags to yylex */ +#define CONTIN BIT(0) /* skip new lines to complete command */ +#define ONEWORD BIT(1) /* single word for substitute() */ +#define ALIAS BIT(2) /* recognise alias */ +#define KEYWORD BIT(3) /* recognise keywords */ +#define LETEXPR BIT(4) /* get expression inside (( )) */ +#define VARASN BIT(5) /* check for var=word */ +#define ARRAYVAR BIT(6) /* parse x[1 & 2] as one word */ +#define ESACONLY BIT(7) /* only accept esac keyword */ +#define CMDWORD BIT(8) /* parsing simple command (alias related) */ +#define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */ +#define LQCHAR BIT(10) /* source string contains QCHAR */ +#define HEREDOC BIT(11) /* parsing a here document */ +#define LETARRAY BIT(12) /* copy expression inside =( ) */ + +#define HERES 10 /* max << in line */ + +#undef CTRL +#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ +#define UNCTRL(x) ((x) ^ 0x40) /* ASCII */ + +EXTERN Source *source; /* yyparse/yylex source */ +EXTERN YYSTYPE yylval; /* result from yylex */ +EXTERN struct ioword *heres [HERES], **herep; +EXTERN char ident [IDENT+1]; + +#define HISTORYSIZE 500 /* size of saved history */ + +EXTERN char **history; /* saved commands */ +EXTERN char **histptr; /* last history item */ +EXTERN int histsize; /* history size */ + +/* user and system time of last j_waitjed job */ +EXTERN struct timeval j_usrtime, j_systime; + +/* lalloc.c */ +void ainit(Area *); +void afreeall(Area *); +/* these cannot fail and can take NULL (not for ap) */ +#define alloc(n, ap) aresize(NULL, (n), (ap)) +void *aresize(void *, size_t, Area *); +void afree(void *, Area *); /* can take NULL */ +/* edit.c */ +#ifndef MKSH_SMALL +int x_bind(const char *, const char *, bool, bool); +#else +int x_bind(const char *, const char *, bool); +#endif +void x_init(void); +int x_read(char *, size_t); +/* eval.c */ +char *substitute(const char *, int); +char **eval(const char **, int); +char *evalstr(const char *cp, int); +char *evalonestr(const char *cp, int); +char *debunk(char *, const char *, size_t); +void expand(const char *, XPtrV *, int); +int glob_str(char *, XPtrV *, int); +/* exec.c */ +int execute(struct op * volatile, volatile int, volatile int * volatile); +int shcomexec(const char **); +struct tbl *findfunc(const char *, uint32_t, bool); +int define(const char *, struct op *); +void builtin(const char *, int (*)(const char **)); +struct tbl *findcom(const char *, int); +void flushcom(int); +const char *search(const char *, const char *, int, int *); +int search_access(const char *, int, int *); +int pr_menu(const char * const *); +int pr_list(char * const *); +/* expr.c */ +int evaluate(const char *, mksh_ari_t *, int, bool); +int v_evaluate(struct tbl *, const char *, volatile int, bool); +/* UTF-8 stuff */ +size_t utf_mbtowc(unsigned int *, const char *); +size_t utf_wctomb(char *, unsigned int); +int utf_widthadj(const char *, const char **); +int utf_mbswidth(const char *); +const char *utf_skipcols(const char *, int); +size_t utf_ptradj(const char *); +#ifndef MKSH_mirbsd_wcwidth +int utf_wcwidth(unsigned int); +#endif +/* funcs.c */ +int c_hash(const char **); +int c_cd(const char **); +int c_pwd(const char **); +int c_print(const char **); +#ifdef MKSH_PRINTF_BUILTIN +int c_printf(const char **); +#endif +int c_whence(const char **); +int c_command(const char **); +int c_typeset(const char **); +int c_alias(const char **); +int c_unalias(const char **); +int c_let(const char **); +int c_jobs(const char **); +#ifndef MKSH_UNEMPLOYED +int c_fgbg(const char **); +#endif +int c_kill(const char **); +void getopts_reset(int); +int c_getopts(const char **); +int c_bind(const char **); +int c_label(const char **); +int c_shift(const char **); +int c_umask(const char **); +int c_dot(const char **); +int c_wait(const char **); +int c_read(const char **); +int c_eval(const char **); +int c_trap(const char **); +int c_brkcont(const char **); +int c_exitreturn(const char **); +int c_set(const char **); +int c_unset(const char **); +int c_ulimit(const char **); +int c_times(const char **); +int timex(struct op *, int, volatile int *); +void timex_hook(struct op *, char ** volatile *); +int c_exec(const char **); +int c_builtin(const char **); +int c_test(const char **); +#if HAVE_MKNOD +int c_mknod(const char **); +#endif +int c_realpath(const char **); +int c_rename(const char **); +/* histrap.c */ +void init_histvec(void); +void hist_init(Source *); +#if HAVE_PERSISTENT_HISTORY +void hist_finish(void); +#endif +void histsave(int *, const char *, bool, bool); +#if !defined(MKSH_SMALL) && HAVE_PERSISTENT_HISTORY +bool histsync(void); +#endif +int c_fc(const char **); +void sethistsize(int); +#if HAVE_PERSISTENT_HISTORY +void sethistfile(const char *); +#endif +char **histpos(void); +int histnum(int); +int findhist(int, int, const char *, int); +int findhistrel(const char *); +char **hist_get_newest(bool); +void inittraps(void); +void alarm_init(void); +Trap *gettrap(const char *, int); +void trapsig(int); +void intrcheck(void); +int fatal_trap_check(void); +int trap_pending(void); +void runtraps(int intr); +void runtrap(Trap *); +void cleartraps(void); +void restoresigs(void); +void settrap(Trap *, const char *); +int block_pipe(void); +void restore_pipe(int); +int setsig(Trap *, sig_t, int); +void setexecsig(Trap *, int); +/* jobs.c */ +void j_init(void); +void j_exit(void); +#ifndef MKSH_UNEMPLOYED +void j_change(void); +#endif +int exchild(struct op *, int, volatile int *, int); +void startlast(void); +int waitlast(void); +int waitfor(const char *, int *); +int j_kill(const char *, int); +#ifndef MKSH_UNEMPLOYED +int j_resume(const char *, int); +#endif +int j_jobs(const char *, int, int); +int j_njobs(void); +void j_notify(void); +pid_t j_async(void); +int j_stopped_running(void); +/* lex.c */ +int yylex(int); +void yyerror(const char *, ...) + MKSH_A_NORETURN + MKSH_A_FORMAT(printf, 1, 2); +Source *pushs(int, Area *); +void set_prompt(int, Source *); +void pprompt(const char *, int); +int promptlen(const char *); +/* main.c */ +int include(const char *, int, const char **, int); +int command(const char *, int); +int shell(Source *volatile, int volatile); +void unwind(int) MKSH_A_NORETURN; +void newenv(int); +void quitenv(struct shf *); +void cleanup_parents_env(void); +void cleanup_proc_env(void); +void errorf(const char *, ...) + MKSH_A_NORETURN + MKSH_A_FORMAT(printf, 1, 2); +void warningf(bool, const char *, ...) + MKSH_A_FORMAT(printf, 2, 3); +void bi_errorf(const char *, ...) + MKSH_A_FORMAT(printf, 1, 2); +#define errorfz() errorf("\1") +#define bi_errorfz() bi_errorf("\1") +void internal_verrorf(const char *, va_list) + MKSH_A_FORMAT(printf, 1, 0); +void internal_errorf(const char *, ...) + MKSH_A_NORETURN + MKSH_A_FORMAT(printf, 1, 2); +void internal_warningf(const char *, ...) + MKSH_A_FORMAT(printf, 1, 2); +void error_prefix(bool); +void shellf(const char *, ...) + MKSH_A_FORMAT(printf, 1, 2); +void shprintf(const char *, ...) + MKSH_A_FORMAT(printf, 1, 2); +int can_seek(int); +void initio(void); +int ksh_dup2(int, int, bool); +short savefd(int); +void restfd(int, int); +void openpipe(int *); +void closepipe(int *); +int check_fd(const char *, int, const char **); +void coproc_init(void); +void coproc_read_close(int); +void coproc_readw_close(int); +void coproc_write_close(int); +int coproc_getfd(int, const char **); +void coproc_cleanup(int); +struct temp *maketemp(Area *, Temp_type, struct temp **); +#define hash(s) oaathash_full((const uint8_t *)(s)) +uint32_t oaathash_full(register const uint8_t *); +uint32_t hashmem(const void *, size_t); +void ktinit(struct table *, Area *, size_t); +struct tbl *ktsearch(struct table *, const char *, uint32_t); +struct tbl *ktenter(struct table *, const char *, uint32_t); +#define ktdelete(p) do { p->flag = 0; } while (/* CONSTCOND */ 0) +void ktwalk(struct tstate *, struct table *); +struct tbl *ktnext(struct tstate *); +struct tbl **ktsort(struct table *); +/* misc.c */ +void setctypes(const char *, int); +void initctypes(void); +size_t option(const char *); +char *getoptions(void); +void change_flag(enum sh_flag, int, unsigned int); +int parse_args(const char **, int, bool *); +int getn(const char *, int *); +int bi_getn(const char *, int *); +int gmatchx(const char *, const char *, bool); +int has_globbing(const char *, const char *); +const unsigned char *pat_scan(const unsigned char *, const unsigned char *, int); +int xstrcmp(const void *, const void *); +void ksh_getopt_reset(Getopt *, int); +int ksh_getopt(const char **, Getopt *, const char *); +void print_value_quoted(const char *); +void print_columns(struct shf *, int, + char *(*)(char *, int, int, const void *), + const void *, int, int, bool); +void strip_nuls(char *, int); +int blocking_read(int, char *, int) + MKSH_A_BOUNDED(buffer, 2, 3); +int reset_nonblock(int); +char *ksh_get_wd(size_t *); +int make_path(const char *, const char *, char **, XString *, int *); +void simplify_path(char *); +void set_current_wd(char *); +#ifdef MKSH_SMALL +char *strdup_(const char *, Area *); +char *strndup_(const char *, size_t, Area *); +#endif +int unbksl(bool, int (*)(void), void (*)(int)); +/* shf.c */ +struct shf *shf_open(const char *, int, int, int); +struct shf *shf_fdopen(int, int, struct shf *); +struct shf *shf_reopen(int, int, struct shf *); +struct shf *shf_sopen(char *, int, int, struct shf *); +int shf_close(struct shf *); +int shf_fdclose(struct shf *); +char *shf_sclose(struct shf *); +int shf_flush(struct shf *); +int shf_read(char *, int, struct shf *); +char *shf_getse(char *, int, struct shf *); +int shf_getchar(struct shf *s); +int shf_ungetc(int, struct shf *); +int shf_putchar(int, struct shf *); +int shf_puts(const char *, struct shf *); +int shf_write(const char *, int, struct shf *); +int shf_fprintf(struct shf *, const char *, ...) + MKSH_A_FORMAT(printf, 2, 3); +int shf_snprintf(char *, int, const char *, ...) + MKSH_A_FORMAT(printf, 3, 4) + MKSH_A_BOUNDED(string, 1, 2); +char *shf_smprintf(const char *, ...) + MKSH_A_FORMAT(printf, 1, 2); +int shf_vfprintf(struct shf *, const char *, va_list) + MKSH_A_FORMAT(printf, 2, 0); +/* syn.c */ +void initkeywords(void); +struct op *compile(Source *); +/* tree.c */ +int fptreef(struct shf *, int, const char *, ...); +char *snptreef(char *, int, const char *, ...); +struct op *tcopy(struct op *, Area *); +char *wdcopy(const char *, Area *); +const char *wdscan(const char *, int); +char *wdstrip(const char *, bool, bool); +void tfree(struct op *, Area *); +/* var.c */ +void newblock(void); +void popblock(void); +void initvar(void); +struct tbl *global(const char *); +struct tbl *local(const char *, bool); +char *str_val(struct tbl *); +int setstr(struct tbl *, const char *, int); +struct tbl *setint_v(struct tbl *, struct tbl *, bool); +void setint(struct tbl *, mksh_ari_t); +struct tbl *typeset(const char *, Tflag, Tflag, int, int) + MKSH_A_NONNULL((nonnull (1))); +void unset(struct tbl *, int); +const char *skip_varname(const char *, int); +const char *skip_wdvarname(const char *, int); +int is_wdvarname(const char *, int); +int is_wdvarassign(const char *); +char **makenv(void); +void change_random(const void *, size_t); +void change_winsz(void); +int array_ref_len(const char *); +char *arrayname(const char *); +mksh_uari_t set_array(const char *, bool, const char **); + +enum Test_op { + TO_NONOP = 0, /* non-operator */ + /* unary operators */ + TO_STNZE, TO_STZER, TO_OPTION, + TO_FILAXST, + TO_FILEXST, + TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK, + TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID, + TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX, + /* binary operators */ + TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT, + TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT, + /* not an operator */ + TO_NONNULL /* !TO_NONOP */ +}; +typedef enum Test_op Test_op; + +/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */ +enum Test_meta { + TM_OR, /* -o or || */ + TM_AND, /* -a or && */ + TM_NOT, /* ! */ + TM_OPAREN, /* ( */ + TM_CPAREN, /* ) */ + TM_UNOP, /* unary operator */ + TM_BINOP, /* binary operator */ + TM_END /* end of input */ +}; +typedef enum Test_meta Test_meta; + +#define TEF_ERROR BIT(0) /* set if we've hit an error */ +#define TEF_DBRACKET BIT(1) /* set if [[ .. ]] test */ + +typedef struct test_env { + union { + const char **wp;/* used by ptest_* */ + XPtrV *av; /* used by dbtestp_* */ + } pos; + const char **wp_end; /* used by ptest_* */ + Test_op (*isa)(struct test_env *, Test_meta); + const char *(*getopnd) (struct test_env *, Test_op, bool); + int (*eval)(struct test_env *, Test_op, const char *, const char *, bool); + void (*error)(struct test_env *, int, const char *); + int flags; /* TEF_* */ +} Test_env; + +extern const char *const dbtest_tokens[]; + +Test_op test_isop(Test_meta, const char *); +int test_eval(Test_env *, Test_op, const char *, const char *, bool); +int test_parse(Test_env *); + +EXTERN int tty_fd I__(-1); /* dup'd tty file descriptor */ +EXTERN int tty_devtty; /* true if tty_fd is from /dev/tty */ +EXTERN struct termios tty_state; /* saved tty state */ + +extern void tty_init(bool, bool); +extern void tty_close(void); + +/* be sure not to interfere with anyone else's idea about EXTERN */ +#ifdef EXTERN_DEFINED +# undef EXTERN_DEFINED +# undef EXTERN +#endif +#undef I__ + +#endif /* !MKSH_INCLUDES_ONLY */ diff --git a/mksh/src/sh_flags.h b/mksh/src/sh_flags.h new file mode 100644 index 0000000..aa5481e --- /dev/null +++ b/mksh/src/sh_flags.h @@ -0,0 +1,145 @@ +#if defined(SHFLAGS_DEFNS) +__RCSID("$MirOS: src/bin/mksh/sh_flags.h,v 1.7 2010/07/13 13:07:58 tg Exp $"); +#define FN(sname,cname,ochar,flags) /* nothing */ +#elif defined(SHFLAGS_ENUMS) +#define FN(sname,cname,ochar,flags) cname, +#define F0(sname,cname,ochar,flags) cname = 0, +#elif defined(SHFLAGS_ITEMS) +#define FN(sname,cname,ochar,flags) { sname, ochar, flags }, +#endif + +#ifndef F0 +#define F0 FN +#endif + +/* + * special cases (see parse_args()): -A, -o, -s + * + * options are sorted by their longnames + */ + +/* -a all new parameters are created with the export attribute */ +F0("allexport", FEXPORT, 'a', OF_ANY) + +/* ./. backwards compat: dummy, emits a warning */ +FN("arc4random", FARC4RANDOM, 0, OF_ANY) + +#if HAVE_NICE +/* ./. bgnice */ +FN("bgnice", FBGNICE, 0, OF_ANY) +#endif + +/* ./. enable {} globbing (non-standard) */ +FN("braceexpand", FBRACEEXPAND, 0, OF_ANY) + +/* ./. Emacs command line editing mode */ +FN("emacs", FEMACS, 0, OF_ANY) + +/* -e quit on error */ +FN("errexit", FERREXIT, 'e', OF_ANY) + +/* ./. Emacs command line editing mode, gmacs variant */ +FN("gmacs", FGMACS, 0, OF_ANY) + +/* ./. reading EOF does not exit */ +FN("ignoreeof", FIGNOREEOF, 0, OF_ANY) + +/* -i interactive shell */ +FN("interactive", FTALKING, 'i', OF_CMDLINE) + +/* -k name=value are recognised anywhere */ +FN("keyword", FKEYWORD, 'k', OF_ANY) + +/* -l login shell */ +FN("login", FLOGIN, 'l', OF_CMDLINE) + +/* -X mark dirs with / in file name completion */ +FN("markdirs", FMARKDIRS, 'X', OF_ANY) + +#ifndef MKSH_UNEMPLOYED +/* -m job control monitoring */ +FN("monitor", FMONITOR, 'm', OF_ANY) +#endif + +/* -C don't overwrite existing files */ +FN("noclobber", FNOCLOBBER, 'C', OF_ANY) + +/* -n don't execute any commands */ +FN("noexec", FNOEXEC, 'n', OF_ANY) + +/* -f don't do file globbing */ +FN("noglob", FNOGLOB, 'f', OF_ANY) + +/* ./. don't kill running jobs when login shell exits */ +FN("nohup", FNOHUP, 0, OF_ANY) + +/* ./. don't save functions in history (no effect) */ +FN("nolog", FNOLOG, 0, OF_ANY) + +#ifndef MKSH_UNEMPLOYED +/* -b asynchronous job completion notification */ +FN("notify", FNOTIFY, 'b', OF_ANY) +#endif + +/* -u using an unset variable is an error */ +FN("nounset", FNOUNSET, 'u', OF_ANY) + +/* ./. don't do logical cds/pwds (non-standard) */ +FN("physical", FPHYSICAL, 0, OF_ANY) + +/* ./. pdksh compat: somewhat more POSIXish mode (non-standard) */ +FN("posix", FPOSIX, 0, OF_ANY) + +/* -p use suid_profile; privileged shell */ +FN("privileged", FPRIVILEGED, 'p', OF_ANY) + +/* -r restricted shell */ +FN("restricted", FRESTRICTED, 'r', OF_CMDLINE) + +/* ./. pdksh compat: called as sh not mksh; kludge mode (non-standard) */ +FN("sh", FSH, 0, OF_ANY) + +/* -s (invocation) parse stdin (pseudo non-standard) */ +FN("stdin", FSTDIN, 's', OF_CMDLINE) + +/* -h create tracked aliases for all commands */ +FN("trackall", FTRACKALL, 'h', OF_ANY) + +/* -U enable UTF-8 processing (non-standard) */ +FN("utf8-mode", FUNICODE, 'U', OF_ANY) + +/* -v echo input */ +FN("verbose", FVERBOSE, 'v', OF_ANY) + +#if !MKSH_S_NOVI +/* ./. Vi command line editing mode */ +FN("vi", FVI, 0, OF_ANY) + +/* ./. enable ESC as file name completion character (non-standard) */ +FN("vi-esccomplete", FVIESCCOMPLETE, 0, OF_ANY) + +/* ./. enable Tab as file name completion character (non-standard) */ +FN("vi-tabcomplete", FVITABCOMPLETE, 0, OF_ANY) + +/* ./. always read in raw mode (no effect) */ +FN("viraw", FVIRAW, 0, OF_ANY) +#endif + +/* -x execution trace (display commands as they are run) */ +FN("xtrace", FXTRACE, 'x', OF_ANY) + +/* -c (invocation) execute specified command */ +FN(NULL, FCOMMAND, 'c', OF_CMDLINE) + +/* + * anonymous flags: used internally by shell only (not visible to user) + */ + +/* ./. (internal) initial shell was interactive */ +FN(NULL, FTALKING_I, 0, OF_INTERNAL) + +#undef FN +#undef F0 +#undef SHFLAGS_DEFNS +#undef SHFLAGS_ENUMS +#undef SHFLAGS_ITEMS diff --git a/mksh/src/shf.c b/mksh/src/shf.c new file mode 100644 index 0000000..0962752 --- /dev/null +++ b/mksh/src/shf.c @@ -0,0 +1,1042 @@ +/* $OpenBSD: shf.c,v 1.15 2006/04/02 00:48:33 deraadt Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.36 2010/07/19 22:41:04 tg Exp $"); + +/* flags to shf_emptybuf() */ +#define EB_READSW 0x01 /* about to switch to reading */ +#define EB_GROW 0x02 /* grow buffer if necessary (STRING+DYNAMIC) */ + +/* + * Replacement stdio routines. Stdio is too flakey on too many machines + * to be useful when you have multiple processes using the same underlying + * file descriptors. + */ + +static int shf_fillbuf(struct shf *); +static int shf_emptybuf(struct shf *, int); + +/* Open a file. First three args are for open(), last arg is flags for + * this package. Returns NULL if file could not be opened, or if a dup + * fails. + */ +struct shf * +shf_open(const char *name, int oflags, int mode, int sflags) +{ + struct shf *shf; + int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + int fd; + + /* Done before open so if alloca fails, fd won't be lost. */ + shf = alloc(sizeof(struct shf) + bsize, ATEMP); + shf->areap = ATEMP; + shf->buf = (unsigned char *)&shf[1]; + shf->bsize = bsize; + shf->flags = SHF_ALLOCS; + /* Rest filled in by reopen. */ + + fd = open(name, oflags, mode); + if (fd < 0) { + afree(shf, shf->areap); + return (NULL); + } + if ((sflags & SHF_MAPHI) && fd < FDBASE) { + int nfd; + + nfd = fcntl(fd, F_DUPFD, FDBASE); + close(fd); + if (nfd < 0) { + afree(shf, shf->areap); + return (NULL); + } + fd = nfd; + } + sflags &= ~SHF_ACCMODE; + sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD : + ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR : SHF_RDWR); + + return (shf_reopen(fd, sflags, shf)); +} + +/* Set up the shf structure for a file descriptor. Doesn't fail. */ +struct shf * +shf_fdopen(int fd, int sflags, struct shf *shf) +{ + int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + + /* use fcntl() to figure out correct read/write flags */ + if (sflags & SHF_GETFL) { + int flags = fcntl(fd, F_GETFL, 0); + + if (flags < 0) + /* will get an error on first read/write */ + sflags |= SHF_RDWR; + else { + switch (flags & O_ACCMODE) { + case O_RDONLY: + sflags |= SHF_RD; + break; + case O_WRONLY: + sflags |= SHF_WR; + break; + case O_RDWR: + sflags |= SHF_RDWR; + break; + } + } + } + + if (!(sflags & (SHF_RD | SHF_WR))) + internal_errorf("shf_fdopen: missing read/write"); + + if (shf) { + if (bsize) { + shf->buf = alloc(bsize, ATEMP); + sflags |= SHF_ALLOCB; + } else + shf->buf = NULL; + } else { + shf = alloc(sizeof(struct shf) + bsize, ATEMP); + shf->buf = (unsigned char *)&shf[1]; + sflags |= SHF_ALLOCS; + } + shf->areap = ATEMP; + shf->fd = fd; + shf->rp = shf->wp = shf->buf; + shf->rnleft = 0; + shf->rbsize = bsize; + shf->wnleft = 0; /* force call to shf_emptybuf() */ + shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; + shf->flags = sflags; + shf->errno_ = 0; + shf->bsize = bsize; + if (sflags & SHF_CLEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); + return (shf); +} + +/* Set up an existing shf (and buffer) to use the given fd */ +struct shf * +shf_reopen(int fd, int sflags, struct shf *shf) +{ + int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; + + /* use fcntl() to figure out correct read/write flags */ + if (sflags & SHF_GETFL) { + int flags = fcntl(fd, F_GETFL, 0); + + if (flags < 0) + /* will get an error on first read/write */ + sflags |= SHF_RDWR; + else { + switch (flags & O_ACCMODE) { + case O_RDONLY: + sflags |= SHF_RD; + break; + case O_WRONLY: + sflags |= SHF_WR; + break; + case O_RDWR: + sflags |= SHF_RDWR; + break; + } + } + } + + if (!(sflags & (SHF_RD | SHF_WR))) + internal_errorf("shf_reopen: missing read/write"); + if (!shf || !shf->buf || shf->bsize < bsize) + internal_errorf("shf_reopen: bad shf/buf/bsize"); + + /* assumes shf->buf and shf->bsize already set up */ + shf->fd = fd; + shf->rp = shf->wp = shf->buf; + shf->rnleft = 0; + shf->rbsize = bsize; + shf->wnleft = 0; /* force call to shf_emptybuf() */ + shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; + shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags; + shf->errno_ = 0; + if (sflags & SHF_CLEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); + return (shf); +} + +/* Open a string for reading or writing. If reading, bsize is the number + * of bytes that can be read. If writing, bsize is the maximum number of + * bytes that can be written. If shf is not null, it is filled in and + * returned, if it is null, shf is allocated. If writing and buf is null + * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is + * used for the initial size). Doesn't fail. + * When writing, a byte is reserved for a trailing null - see shf_sclose(). + */ +struct shf * +shf_sopen(char *buf, int bsize, int sflags, struct shf *shf) +{ + /* can't have a read+write string */ + if (!(!(sflags & SHF_RD) ^ !(sflags & SHF_WR))) + internal_errorf("shf_sopen: flags 0x%x", sflags); + + if (!shf) { + shf = alloc(sizeof(struct shf), ATEMP); + sflags |= SHF_ALLOCS; + } + shf->areap = ATEMP; + if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) { + if (bsize <= 0) + bsize = 64; + sflags |= SHF_ALLOCB; + buf = alloc(bsize, shf->areap); + } + shf->fd = -1; + shf->buf = shf->rp = shf->wp = (unsigned char *)buf; + shf->rnleft = bsize; + shf->rbsize = bsize; + shf->wnleft = bsize - 1; /* space for a '\0' */ + shf->wbsize = bsize; + shf->flags = sflags | SHF_STRING; + shf->errno_ = 0; + shf->bsize = bsize; + + return (shf); +} + +/* Flush and close file descriptor, free the shf structure */ +int +shf_close(struct shf *shf) +{ + int ret = 0; + + if (shf->fd >= 0) { + ret = shf_flush(shf); + if (close(shf->fd) < 0) + ret = EOF; + } + if (shf->flags & SHF_ALLOCS) + afree(shf, shf->areap); + else if (shf->flags & SHF_ALLOCB) + afree(shf->buf, shf->areap); + + return (ret); +} + +/* Flush and close file descriptor, don't free file structure */ +int +shf_fdclose(struct shf *shf) +{ + int ret = 0; + + if (shf->fd >= 0) { + ret = shf_flush(shf); + if (close(shf->fd) < 0) + ret = EOF; + shf->rnleft = 0; + shf->rp = shf->buf; + shf->wnleft = 0; + shf->fd = -1; + } + + return (ret); +} + +/* Close a string - if it was opened for writing, it is null terminated; + * returns a pointer to the string and frees shf if it was allocated + * (does not free string if it was allocated). + */ +char * +shf_sclose(struct shf *shf) +{ + unsigned char *s = shf->buf; + + /* null terminate */ + if (shf->flags & SHF_WR) { + shf->wnleft++; + shf_putc('\0', shf); + } + if (shf->flags & SHF_ALLOCS) + afree(shf, shf->areap); + return ((char *)s); +} + +/* Un-read what has been read but not examined, or write what has been + * buffered. Returns 0 for success, EOF for (write) error. + */ +int +shf_flush(struct shf *shf) +{ + if (shf->flags & SHF_STRING) + return ((shf->flags & SHF_WR) ? EOF : 0); + + if (shf->fd < 0) + internal_errorf("shf_flush: no fd"); + + if (shf->flags & SHF_ERROR) { + errno = shf->errno_; + return (EOF); + } + + if (shf->flags & SHF_READING) { + shf->flags &= ~(SHF_EOF | SHF_READING); + if (shf->rnleft > 0) { + lseek(shf->fd, (off_t)-shf->rnleft, SEEK_CUR); + shf->rnleft = 0; + shf->rp = shf->buf; + } + return (0); + } else if (shf->flags & SHF_WRITING) + return (shf_emptybuf(shf, 0)); + + return (0); +} + +/* Write out any buffered data. If currently reading, flushes the read + * buffer. Returns 0 for success, EOF for (write) error. + */ +static int +shf_emptybuf(struct shf *shf, int flags) +{ + int ret = 0; + + if (!(shf->flags & SHF_STRING) && shf->fd < 0) + internal_errorf("shf_emptybuf: no fd"); + + if (shf->flags & SHF_ERROR) { + errno = shf->errno_; + return (EOF); + } + + if (shf->flags & SHF_READING) { + if (flags & EB_READSW) /* doesn't happen */ + return (0); + ret = shf_flush(shf); + shf->flags &= ~SHF_READING; + } + if (shf->flags & SHF_STRING) { + unsigned char *nbuf; + + /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB + * is set... (changing the shf pointer could cause problems) + */ + if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) || + !(shf->flags & SHF_ALLOCB)) + return (EOF); + /* allocate more space for buffer */ + nbuf = aresize(shf->buf, 2 * shf->wbsize, shf->areap); + shf->rp = nbuf + (shf->rp - shf->buf); + shf->wp = nbuf + (shf->wp - shf->buf); + shf->rbsize += shf->wbsize; + shf->wnleft += shf->wbsize; + shf->wbsize *= 2; + shf->buf = nbuf; + } else { + if (shf->flags & SHF_WRITING) { + int ntowrite = shf->wp - shf->buf; + unsigned char *buf = shf->buf; + int n; + + while (ntowrite > 0) { + n = write(shf->fd, buf, ntowrite); + if (n < 0) { + if (errno == EINTR && + !(shf->flags & SHF_INTERRUPT)) + continue; + shf->flags |= SHF_ERROR; + shf->errno_ = errno; + shf->wnleft = 0; + if (buf != shf->buf) { + /* allow a second flush + * to work */ + memmove(shf->buf, buf, + ntowrite); + shf->wp = shf->buf + ntowrite; + } + return (EOF); + } + buf += n; + ntowrite -= n; + } + if (flags & EB_READSW) { + shf->wp = shf->buf; + shf->wnleft = 0; + shf->flags &= ~SHF_WRITING; + return (0); + } + } + shf->wp = shf->buf; + shf->wnleft = shf->wbsize; + } + shf->flags |= SHF_WRITING; + + return (ret); +} + +/* Fill up a read buffer. Returns EOF for a read error, 0 otherwise. */ +static int +shf_fillbuf(struct shf *shf) +{ + if (shf->flags & SHF_STRING) + return (0); + + if (shf->fd < 0) + internal_errorf("shf_fillbuf: no fd"); + + if (shf->flags & (SHF_EOF | SHF_ERROR)) { + if (shf->flags & SHF_ERROR) + errno = shf->errno_; + return (EOF); + } + + if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) + return (EOF); + + shf->flags |= SHF_READING; + + shf->rp = shf->buf; + while (1) { + shf->rnleft = blocking_read(shf->fd, (char *) shf->buf, + shf->rbsize); + if (shf->rnleft < 0 && errno == EINTR && + !(shf->flags & SHF_INTERRUPT)) + continue; + break; + } + if (shf->rnleft <= 0) { + if (shf->rnleft < 0) { + shf->flags |= SHF_ERROR; + shf->errno_ = errno; + shf->rnleft = 0; + shf->rp = shf->buf; + return (EOF); + } + shf->flags |= SHF_EOF; + } + return (0); +} + +/* Read a buffer from shf. Returns the number of bytes read into buf, + * if no bytes were read, returns 0 if end of file was seen, EOF if + * a read error occurred. + */ +int +shf_read(char *buf, int bsize, struct shf *shf) +{ + int orig_bsize = bsize; + int ncopy; + + if (!(shf->flags & SHF_RD)) + internal_errorf("shf_read: flags %x", shf->flags); + + if (bsize <= 0) + internal_errorf("shf_read: bsize %d", bsize); + + while (bsize > 0) { + if (shf->rnleft == 0 && + (shf_fillbuf(shf) == EOF || shf->rnleft == 0)) + break; + ncopy = shf->rnleft; + if (ncopy > bsize) + ncopy = bsize; + memcpy(buf, shf->rp, ncopy); + buf += ncopy; + bsize -= ncopy; + shf->rp += ncopy; + shf->rnleft -= ncopy; + } + /* Note: fread(3S) returns 0 for errors - this doesn't */ + return (orig_bsize == bsize ? (shf_error(shf) ? EOF : 0) : + orig_bsize - bsize); +} + +/* Read up to a newline or EOF. The newline is put in buf; buf is always + * null terminated. Returns NULL on read error or if nothing was read before + * end of file, returns a pointer to the null byte in buf otherwise. + */ +char * +shf_getse(char *buf, int bsize, struct shf *shf) +{ + unsigned char *end; + int ncopy; + char *orig_buf = buf; + + if (!(shf->flags & SHF_RD)) + internal_errorf("shf_getse: flags %x", shf->flags); + + if (bsize <= 0) + return (NULL); + + --bsize; /* save room for null */ + do { + if (shf->rnleft == 0) { + if (shf_fillbuf(shf) == EOF) + return (NULL); + if (shf->rnleft == 0) { + *buf = '\0'; + return (buf == orig_buf ? NULL : buf); + } + } + end = (unsigned char *)memchr((char *) shf->rp, '\n', + shf->rnleft); + ncopy = end ? end - shf->rp + 1 : shf->rnleft; + if (ncopy > bsize) + ncopy = bsize; + memcpy(buf, (char *) shf->rp, ncopy); + shf->rp += ncopy; + shf->rnleft -= ncopy; + buf += ncopy; + bsize -= ncopy; + } while (!end && bsize); + *buf = '\0'; + return (buf); +} + +/* Returns the char read. Returns EOF for error and end of file. */ +int +shf_getchar(struct shf *shf) +{ + if (!(shf->flags & SHF_RD)) + internal_errorf("shf_getchar: flags %x", shf->flags); + + if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0)) + return (EOF); + --shf->rnleft; + return (*shf->rp++); +} + +/* Put a character back in the input stream. Returns the character if + * successful, EOF if there is no room. + */ +int +shf_ungetc(int c, struct shf *shf) +{ + if (!(shf->flags & SHF_RD)) + internal_errorf("shf_ungetc: flags %x", shf->flags); + + if ((shf->flags & SHF_ERROR) || c == EOF || + (shf->rp == shf->buf && shf->rnleft)) + return (EOF); + + if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) + return (EOF); + + if (shf->rp == shf->buf) + shf->rp = shf->buf + shf->rbsize; + if (shf->flags & SHF_STRING) { + /* Can unget what was read, but not something different - we + * don't want to modify a string. + */ + if (shf->rp[-1] != c) + return (EOF); + shf->flags &= ~SHF_EOF; + shf->rp--; + shf->rnleft++; + return (c); + } + shf->flags &= ~SHF_EOF; + *--(shf->rp) = c; + shf->rnleft++; + return (c); +} + +/* Write a character. Returns the character if successful, EOF if + * the char could not be written. + */ +int +shf_putchar(int c, struct shf *shf) +{ + if (!(shf->flags & SHF_WR)) + internal_errorf("shf_putchar: flags %x", shf->flags); + + if (c == EOF) + return (EOF); + + if (shf->flags & SHF_UNBUF) { + unsigned char cc = (unsigned char)c; + int n; + + if (shf->fd < 0) + internal_errorf("shf_putchar: no fd"); + if (shf->flags & SHF_ERROR) { + errno = shf->errno_; + return (EOF); + } + while ((n = write(shf->fd, &cc, 1)) != 1) + if (n < 0) { + if (errno == EINTR && + !(shf->flags & SHF_INTERRUPT)) + continue; + shf->flags |= SHF_ERROR; + shf->errno_ = errno; + return (EOF); + } + } else { + /* Flush deals with strings and sticky errors */ + if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF) + return (EOF); + shf->wnleft--; + *shf->wp++ = c; + } + + return (c); +} + +/* Write a string. Returns the length of the string if successful, EOF if + * the string could not be written. + */ +int +shf_puts(const char *s, struct shf *shf) +{ + if (!s) + return (EOF); + + return (shf_write(s, strlen(s), shf)); +} + +/* Write a buffer. Returns nbytes if successful, EOF if there is an error. */ +int +shf_write(const char *buf, int nbytes, struct shf *shf) +{ + int n, ncopy, orig_nbytes = nbytes; + + if (!(shf->flags & SHF_WR)) + internal_errorf("shf_write: flags %x", shf->flags); + + if (nbytes < 0) + internal_errorf("shf_write: nbytes %d", nbytes); + + /* Don't buffer if buffer is empty and we're writting a large amount. */ + if ((ncopy = shf->wnleft) && + (shf->wp != shf->buf || nbytes < shf->wnleft)) { + if (ncopy > nbytes) + ncopy = nbytes; + memcpy(shf->wp, buf, ncopy); + nbytes -= ncopy; + buf += ncopy; + shf->wp += ncopy; + shf->wnleft -= ncopy; + } + if (nbytes > 0) { + if (shf->flags & SHF_STRING) { + /* resize buffer until there's enough space left */ + while (nbytes > shf->wnleft) + if (shf_emptybuf(shf, EB_GROW) == EOF) + return (EOF); + /* then write everything into the buffer */ + } else { + /* flush deals with sticky errors */ + if (shf_emptybuf(shf, EB_GROW) == EOF) + return (EOF); + /* write chunks larger than window size directly */ + if (nbytes > shf->wbsize) { + ncopy = nbytes; + if (shf->wbsize) + ncopy -= nbytes % shf->wbsize; + nbytes -= ncopy; + while (ncopy > 0) { + n = write(shf->fd, buf, ncopy); + if (n < 0) { + if (errno == EINTR && + !(shf->flags & SHF_INTERRUPT)) + continue; + shf->flags |= SHF_ERROR; + shf->errno_ = errno; + shf->wnleft = 0; + /* + * Note: fwrite(3) returns 0 + * for errors - this doesn't + */ + return (EOF); + } + buf += n; + ncopy -= n; + } + } + /* ... and buffer the rest */ + } + if (nbytes > 0) { + /* write remaining bytes to buffer */ + memcpy(shf->wp, buf, nbytes); + shf->wp += nbytes; + shf->wnleft -= nbytes; + } + } + + return (orig_nbytes); +} + +int +shf_fprintf(struct shf *shf, const char *fmt, ...) +{ + va_list args; + int n; + + va_start(args, fmt); + n = shf_vfprintf(shf, fmt, args); + va_end(args); + + return (n); +} + +int +shf_snprintf(char *buf, int bsize, const char *fmt, ...) +{ + struct shf shf; + va_list args; + int n; + + if (!buf || bsize <= 0) + internal_errorf("shf_snprintf: buf %p, bsize %d", buf, bsize); + + shf_sopen(buf, bsize, SHF_WR, &shf); + va_start(args, fmt); + n = shf_vfprintf(&shf, fmt, args); + va_end(args); + shf_sclose(&shf); /* null terminates */ + return (n); +} + +char * +shf_smprintf(const char *fmt, ...) +{ + struct shf shf; + va_list args; + + shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf); + va_start(args, fmt); + shf_vfprintf(&shf, fmt, args); + va_end(args); + return (shf_sclose(&shf)); /* null terminates */ +} + +#undef FP /* if you want floating point stuff */ + +#ifndef DMAXEXP +# define DMAXEXP 128 /* should be big enough */ +#endif + +#define BUF_SIZE 128 +/* must be > MAX(DMAXEXP, log10(pow(2, DSIGNIF))) + ceil(log10(DMAXEXP)) + 8 + * (I think); since it's hard to express as a constant, just use a large buffer + */ +#define FPBUF_SIZE (DMAXEXP+16) + +#define FL_HASH 0x001 /* '#' seen */ +#define FL_PLUS 0x002 /* '+' seen */ +#define FL_RIGHT 0x004 /* '-' seen */ +#define FL_BLANK 0x008 /* ' ' seen */ +#define FL_SHORT 0x010 /* 'h' seen */ +#define FL_LONG 0x020 /* 'l' seen */ +#define FL_ZERO 0x040 /* '0' seen */ +#define FL_DOT 0x080 /* '.' seen */ +#define FL_UPPER 0x100 /* format character was uppercase */ +#define FL_NUMBER 0x200 /* a number was formated %[douxefg] */ + + +int +shf_vfprintf(struct shf *shf, const char *fmt, va_list args) +{ + const char *s; + char c, *cp; + int tmp = 0, field, precision, len, flags; + unsigned long lnum; + /* %#o produces the longest output */ + char numbuf[(8 * sizeof(long) + 2) / 3 + 1]; + /* this stuff for dealing with the buffer */ + int nwritten = 0; + + if (!fmt) + return (0); + + while ((c = *fmt++)) { + if (c != '%') { + shf_putc(c, shf); + nwritten++; + continue; + } + /* + * This will accept flags/fields in any order - not + * just the order specified in printf(3), but this is + * the way _doprnt() seems to work (on bsd and sysV). + * The only restriction is that the format character must + * come last :-). + */ + flags = field = precision = 0; + for ( ; (c = *fmt++) ; ) { + switch (c) { + case '#': + flags |= FL_HASH; + continue; + + case '+': + flags |= FL_PLUS; + continue; + + case '-': + flags |= FL_RIGHT; + continue; + + case ' ': + flags |= FL_BLANK; + continue; + + case '0': + if (!(flags & FL_DOT)) + flags |= FL_ZERO; + continue; + + case '.': + flags |= FL_DOT; + precision = 0; + continue; + + case '*': + tmp = va_arg(args, int); + if (flags & FL_DOT) + precision = tmp; + else if ((field = tmp) < 0) { + field = -field; + flags |= FL_RIGHT; + } + continue; + + case 'l': + flags |= FL_LONG; + continue; + + case 'h': + flags |= FL_SHORT; + continue; + } + if (ksh_isdigit(c)) { + tmp = c - '0'; + while (c = *fmt++, ksh_isdigit(c)) + tmp = tmp * 10 + c - '0'; + --fmt; + if (tmp < 0) /* overflow? */ + tmp = 0; + if (flags & FL_DOT) + precision = tmp; + else + field = tmp; + continue; + } + break; + } + + if (precision < 0) + precision = 0; + + if (!c) /* nasty format */ + break; + + if (c >= 'A' && c <= 'Z') { + flags |= FL_UPPER; + c = ksh_tolower(c); + } + + switch (c) { + case 'p': /* pointer */ + flags &= ~(FL_LONG | FL_SHORT); + flags |= (sizeof(char *) > sizeof(int)) ? + /* hope it fits.. */ FL_LONG : 0; + /* aaahhh... */ + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + flags |= FL_NUMBER; + cp = numbuf + sizeof(numbuf); + /*- + * XXX any better way to do this? + * XXX hopefully the compiler optimises this out + * + * For shorts, we want sign extend for %d but not + * for %[oxu] - on 16 bit machines it doesn't matter. + * Assumes C compiler has converted shorts to ints + * before pushing them. XXX optimise this -tg + */ + if (flags & FL_LONG) + lnum = va_arg(args, unsigned long); + else if ((sizeof(int) < sizeof(long)) && (c == 'd')) + lnum = (long)va_arg(args, int); + else + lnum = va_arg(args, unsigned int); + switch (c) { + case 'd': + case 'i': + if (0 > (long)lnum) { + lnum = -(long)lnum; + tmp = 1; + } else + tmp = 0; + /* FALLTHROUGH */ + case 'u': + do { + *--cp = lnum % 10 + '0'; + lnum /= 10; + } while (lnum); + + if (c != 'u') { + if (tmp) + *--cp = '-'; + else if (flags & FL_PLUS) + *--cp = '+'; + else if (flags & FL_BLANK) + *--cp = ' '; + } + break; + + case 'o': + do { + *--cp = (lnum & 0x7) + '0'; + lnum >>= 3; + } while (lnum); + + if ((flags & FL_HASH) && *cp != '0') + *--cp = '0'; + break; + + case 'p': + case 'x': { + const char *digits = (flags & FL_UPPER) ? + digits_uc : digits_lc; + do { + *--cp = digits[lnum & 0xf]; + lnum >>= 4; + } while (lnum); + + if (flags & FL_HASH) { + *--cp = (flags & FL_UPPER) ? 'X' : 'x'; + *--cp = '0'; + } + } + } + len = numbuf + sizeof(numbuf) - (s = cp); + if (flags & FL_DOT) { + if (precision > len) { + field = precision; + flags |= FL_ZERO; + } else + precision = len; /* no loss */ + } + break; + + case 's': + if (!(s = va_arg(args, const char *))) + s = "(null)"; + len = utf_mbswidth(s); + break; + + case 'c': + flags &= ~FL_DOT; + numbuf[0] = (char)(va_arg(args, int)); + s = numbuf; + len = 1; + break; + + case '%': + default: + numbuf[0] = c; + s = numbuf; + len = 1; + break; + } + + /* + * At this point s should point to a string that is to be + * formatted, and len should be the length of the string. + */ + if (!(flags & FL_DOT) || len < precision) + precision = len; + if (field > precision) { + field -= precision; + if (!(flags & FL_RIGHT)) { + field = -field; + /* skip past sign or 0x when padding with 0 */ + if ((flags & FL_ZERO) && (flags & FL_NUMBER)) { + if (*s == '+' || *s == '-' || + *s == ' ') { + shf_putc(*s, shf); + s++; + precision--; + nwritten++; + } else if (*s == '0') { + shf_putc(*s, shf); + s++; + nwritten++; + if (--precision > 0 && + (*s | 0x20) == 'x') { + shf_putc(*s, shf); + s++; + precision--; + nwritten++; + } + } + c = '0'; + } else + c = flags & FL_ZERO ? '0' : ' '; + if (field < 0) { + nwritten += -field; + for ( ; field < 0 ; field++) + shf_putc(c, shf); + } + } else + c = ' '; + } else + field = 0; + + if (precision > 0) { + const char *q; + + nwritten += precision; + q = utf_skipcols(s, precision); + do { + shf_putc(*s, shf); + } while (++s < q); + } + if (field > 0) { + nwritten += field; + for ( ; field > 0 ; --field) + shf_putc(c, shf); + } + } + + return (shf_error(shf) ? EOF : nwritten); +} + +#ifdef MKSH_SMALL +int +shf_getc(struct shf *shf) +{ + return ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ : + shf_getchar(shf)); +} + +int +shf_putc(int c, struct shf *shf) +{ + return ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) : + ((shf)->wnleft--, *(shf)->wp++ = (c))); +} +#endif diff --git a/mksh/src/syn.c b/mksh/src/syn.c new file mode 100644 index 0000000..64b2867 --- /dev/null +++ b/mksh/src/syn.c @@ -0,0 +1,1004 @@ +/* $OpenBSD: syn.c,v 1.28 2008/07/23 16:34:38 jaredy Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.49 2010/07/17 22:09:39 tg Exp $"); + +struct nesting_state { + int start_token; /* token than began nesting (eg, FOR) */ + int start_line; /* line nesting began on */ +}; + +static void yyparse(void); +static struct op *pipeline(int); +static struct op *andor(void); +static struct op *c_list(int); +static struct ioword *synio(int); +static struct op *nested(int, int, int); +static struct op *get_command(int); +static struct op *dogroup(void); +static struct op *thenpart(void); +static struct op *elsepart(void); +static struct op *caselist(void); +static struct op *casepart(int); +static struct op *function_body(char *, bool); +static char **wordlist(void); +static struct op *block(int, struct op *, struct op *, char **); +static struct op *newtp(int); +static void syntaxerr(const char *) MKSH_A_NORETURN; +static void nesting_push(struct nesting_state *, int); +static void nesting_pop(struct nesting_state *); +static int assign_command(char *); +static int inalias(struct source *); +static Test_op dbtestp_isa(Test_env *, Test_meta); +static const char *dbtestp_getopnd(Test_env *, Test_op, bool); +static int dbtestp_eval(Test_env *, Test_op, const char *, + const char *, bool); +static void dbtestp_error(Test_env *, int, const char *) MKSH_A_NORETURN; + +static struct op *outtree; /* yyparse output */ +static struct nesting_state nesting; /* \n changed to ; */ + +static int reject; /* token(cf) gets symbol again */ +static int symbol; /* yylex value */ + +#define REJECT (reject = 1) +#define ACCEPT (reject = 0) +#define token(cf) ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf))) +#define tpeek(cf) ((reject) ? (symbol) : (REJECT, symbol = yylex(cf))) +#define musthave(c,cf) do { if (token(cf) != (c)) syntaxerr(NULL); } while (0) + +static void +yyparse(void) +{ + int c; + + ACCEPT; + + outtree = c_list(source->type == SSTRING); + c = tpeek(0); + if (c == 0 && !outtree) + outtree = newtp(TEOF); + else if (c != '\n' && c != 0) + syntaxerr(NULL); +} + +static struct op * +pipeline(int cf) +{ + struct op *t, *p, *tl = NULL; + + t = get_command(cf); + if (t != NULL) { + while (token(0) == '|') { + if ((p = get_command(CONTIN)) == NULL) + syntaxerr(NULL); + if (tl == NULL) + t = tl = block(TPIPE, t, p, NOWORDS); + else + tl = tl->right = block(TPIPE, tl->right, p, NOWORDS); + } + REJECT; + } + return (t); +} + +static struct op * +andor(void) +{ + struct op *t, *p; + int c; + + t = pipeline(0); + if (t != NULL) { + while ((c = token(0)) == LOGAND || c == LOGOR) { + if ((p = pipeline(CONTIN)) == NULL) + syntaxerr(NULL); + t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS); + } + REJECT; + } + return (t); +} + +static struct op * +c_list(int multi) +{ + struct op *t = NULL, *p, *tl = NULL; + int c, have_sep; + + while (1) { + p = andor(); + /* Token has always been read/rejected at this point, so + * we don't worry about what flags to pass token() + */ + c = token(0); + have_sep = 1; + if (c == '\n' && (multi || inalias(source))) { + if (!p) /* ignore blank lines */ + continue; + } else if (!p) + break; + else if (c == '&' || c == COPROC) + p = block(c == '&' ? TASYNC : TCOPROC, + p, NOBLOCK, NOWORDS); + else if (c != ';') + have_sep = 0; + if (!t) + t = p; + else if (!tl) + t = tl = block(TLIST, t, p, NOWORDS); + else + tl = tl->right = block(TLIST, tl->right, p, NOWORDS); + if (!have_sep) + break; + } + REJECT; + return (t); +} + +static struct ioword * +synio(int cf) +{ + struct ioword *iop; + static struct ioword *nextiop = NULL; + bool ishere; + + if (nextiop != NULL) { + iop = nextiop; + nextiop = NULL; + return (iop); + } + + if (tpeek(cf) != REDIR) + return (NULL); + ACCEPT; + iop = yylval.iop; + ishere = (iop->flag&IOTYPE) == IOHERE; + musthave(LWORD, ishere ? HEREDELIM : 0); + if (ishere) { + iop->delim = yylval.cp; + if (*ident != 0) /* unquoted */ + iop->flag |= IOEVAL; + if (herep > &heres[HERES - 1]) + yyerror("too many <<s\n"); + *herep++ = iop; + } else + iop->name = yylval.cp; + + if (iop->flag & IOBASH) { + char *cp; + + nextiop = alloc(sizeof(*iop), ATEMP); + nextiop->name = cp = alloc(5, ATEMP); + + if (iop->unit > 9) { + *cp++ = CHAR; + *cp++ = '0' + (iop->unit / 10); + } + *cp++ = CHAR; + *cp++ = '0' + (iop->unit % 10); + *cp = EOS; + + iop->flag &= ~IOBASH; + nextiop->unit = 2; + nextiop->flag = IODUP; + nextiop->delim = NULL; + nextiop->heredoc = NULL; + } + return (iop); +} + +static struct op * +nested(int type, int smark, int emark) +{ + struct op *t; + struct nesting_state old_nesting; + + nesting_push(&old_nesting, smark); + t = c_list(true); + musthave(emark, KEYWORD|ALIAS); + nesting_pop(&old_nesting); + return (block(type, t, NOBLOCK, NOWORDS)); +} + +static struct op * +get_command(int cf) +{ + struct op *t; + int c, iopn = 0, syniocf; + struct ioword *iop, **iops; + XPtrV args, vars; + struct nesting_state old_nesting; + + iops = alloc((NUFILE + 1) * sizeof(struct ioword *), ATEMP); + XPinit(args, 16); + XPinit(vars, 16); + + syniocf = KEYWORD|ALIAS; + switch (c = token(cf|KEYWORD|ALIAS|VARASN)) { + default: + REJECT; + afree(iops, ATEMP); + XPfree(args); + XPfree(vars); + return (NULL); /* empty line */ + + case LWORD: + case REDIR: + REJECT; + syniocf &= ~(KEYWORD|ALIAS); + t = newtp(TCOM); + t->lineno = source->line; + while (1) { + cf = (t->u.evalflags ? ARRAYVAR : 0) | + (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD); + switch (tpeek(cf)) { + case REDIR: + while ((iop = synio(cf)) != NULL) { + if (iopn >= NUFILE) + yyerror("too many redirections\n"); + iops[iopn++] = iop; + } + break; + + case LWORD: + ACCEPT; + /* the iopn == 0 and XPsize(vars) == 0 are + * dubious but AT&T ksh acts this way + */ + if (iopn == 0 && XPsize(vars) == 0 && + XPsize(args) == 0 && + assign_command(ident)) + t->u.evalflags = DOVACHECK; + if ((XPsize(args) == 0 || Flag(FKEYWORD)) && + is_wdvarassign(yylval.cp)) + XPput(vars, yylval.cp); + else + XPput(args, yylval.cp); + break; + + case '(': + /* Check for "> foo (echo hi)" which AT&T ksh + * allows (not POSIX, but not disallowed) + */ + afree(t, ATEMP); + if (XPsize(args) == 0 && XPsize(vars) == 0) { + ACCEPT; + goto Subshell; + } +#ifndef MKSH_SMALL + if ((XPsize(args) == 0 || Flag(FKEYWORD)) && + XPsize(vars) == 1 && is_wdvarassign(yylval.cp)) + goto is_wdarrassign; +#endif + /* Must be a function */ + if (iopn != 0 || XPsize(args) != 1 || + XPsize(vars) != 0) + syntaxerr(NULL); + ACCEPT; + /*(*/ + musthave(')', 0); + t = function_body(XPptrv(args)[0], false); + goto Leave; +#ifndef MKSH_SMALL + is_wdarrassign: + { + static const char set_cmd0[] = { + CHAR, 'e', CHAR, 'v', + CHAR, 'a', CHAR, 'l', EOS + }; + static const char set_cmd1[] = { + CHAR, 's', CHAR, 'e', + CHAR, 't', CHAR, ' ', + CHAR, '-', CHAR, 'A', EOS + }; + static const char set_cmd2[] = { + CHAR, '-', CHAR, '-', EOS + }; + char *tcp; + XPfree(vars); + XPinit(vars, 16); + /* + * we know (or rather hope) that yylval.cp + * contains a string "varname=" + */ + tcp = wdcopy(yylval.cp, ATEMP); + tcp[wdscan(tcp, EOS) - tcp - 3] = EOS; + /* now make an array assignment command */ + t = newtp(TCOM); + t->lineno = source->line; + ACCEPT; + XPput(args, wdcopy(set_cmd0, ATEMP)); + XPput(args, wdcopy(set_cmd1, ATEMP)); + XPput(args, tcp); + XPput(args, wdcopy(set_cmd2, ATEMP)); + musthave(LWORD,LETARRAY); + XPput(args, yylval.cp); + break; + } +#endif + + default: + goto Leave; + } + } + Leave: + break; + + case '(': + Subshell: + t = nested(TPAREN, '(', ')'); + break; + + case '{': /*}*/ + t = nested(TBRACE, '{', '}'); + break; + + case MDPAREN: { + int lno; + static const char let_cmd[] = { + CHAR, 'l', CHAR, 'e', + CHAR, 't', EOS + }; + + /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ + lno = source->line; + ACCEPT; + switch (token(LETEXPR)) { + case LWORD: + break; + case '(': /* ) */ + goto Subshell; + default: + syntaxerr(NULL); + } + t = newtp(TCOM); + t->lineno = lno; + XPput(args, wdcopy(let_cmd, ATEMP)); + XPput(args, yylval.cp); + break; + } + + case DBRACKET: /* [[ .. ]] */ + /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ + t = newtp(TDBRACKET); + ACCEPT; + { + Test_env te; + + te.flags = TEF_DBRACKET; + te.pos.av = &args; + te.isa = dbtestp_isa; + te.getopnd = dbtestp_getopnd; + te.eval = dbtestp_eval; + te.error = dbtestp_error; + + test_parse(&te); + } + break; + + case FOR: + case SELECT: + t = newtp((c == FOR) ? TFOR : TSELECT); + musthave(LWORD, ARRAYVAR); + if (!is_wdvarname(yylval.cp, true)) + yyerror("%s: bad identifier\n", + c == FOR ? "for" : "select"); + strdupx(t->str, ident, ATEMP); + nesting_push(&old_nesting, c); + t->vars = wordlist(); + t->left = dogroup(); + nesting_pop(&old_nesting); + break; + + case WHILE: + case UNTIL: + nesting_push(&old_nesting, c); + t = newtp((c == WHILE) ? TWHILE : TUNTIL); + t->left = c_list(true); + t->right = dogroup(); + nesting_pop(&old_nesting); + break; + + case CASE: + t = newtp(TCASE); + musthave(LWORD, 0); + t->str = yylval.cp; + nesting_push(&old_nesting, c); + t->left = caselist(); + nesting_pop(&old_nesting); + break; + + case IF: + nesting_push(&old_nesting, c); + t = newtp(TIF); + t->left = c_list(true); + t->right = thenpart(); + musthave(FI, KEYWORD|ALIAS); + nesting_pop(&old_nesting); + break; + + case BANG: + syniocf &= ~(KEYWORD|ALIAS); + t = pipeline(0); + if (t == NULL) + syntaxerr(NULL); + t = block(TBANG, NOBLOCK, t, NOWORDS); + break; + + case TIME: + syniocf &= ~(KEYWORD|ALIAS); + t = pipeline(0); + if (t) { + t->str = alloc(2, ATEMP); + t->str[0] = '\0'; /* TF_* flags */ + t->str[1] = '\0'; + } + t = block(TTIME, t, NOBLOCK, NOWORDS); + break; + + case FUNCTION: + musthave(LWORD, 0); + t = function_body(yylval.cp, true); + break; + } + + while ((iop = synio(syniocf)) != NULL) { + if (iopn >= NUFILE) + yyerror("too many redirections\n"); + iops[iopn++] = iop; + } + + if (iopn == 0) { + afree(iops, ATEMP); + t->ioact = NULL; + } else { + iops[iopn++] = NULL; + iops = aresize(iops, iopn * sizeof(struct ioword *), ATEMP); + t->ioact = iops; + } + + if (t->type == TCOM || t->type == TDBRACKET) { + XPput(args, NULL); + t->args = (const char **)XPclose(args); + XPput(vars, NULL); + t->vars = (char **) XPclose(vars); + } else { + XPfree(args); + XPfree(vars); + } + + return (t); +} + +static struct op * +dogroup(void) +{ + int c; + struct op *list; + + c = token(CONTIN|KEYWORD|ALIAS); + /* A {...} can be used instead of do...done for for/select loops + * but not for while/until loops - we don't need to check if it + * is a while loop because it would have been parsed as part of + * the conditional command list... + */ + if (c == DO) + c = DONE; + else if (c == '{') + c = '}'; + else + syntaxerr(NULL); + list = c_list(true); + musthave(c, KEYWORD|ALIAS); + return (list); +} + +static struct op * +thenpart(void) +{ + struct op *t; + + musthave(THEN, KEYWORD|ALIAS); + t = newtp(0); + t->left = c_list(true); + if (t->left == NULL) + syntaxerr(NULL); + t->right = elsepart(); + return (t); +} + +static struct op * +elsepart(void) +{ + struct op *t; + + switch (token(KEYWORD|ALIAS|VARASN)) { + case ELSE: + if ((t = c_list(true)) == NULL) + syntaxerr(NULL); + return (t); + + case ELIF: + t = newtp(TELIF); + t->left = c_list(true); + t->right = thenpart(); + return (t); + + default: + REJECT; + } + return (NULL); +} + +static struct op * +caselist(void) +{ + struct op *t, *tl; + int c; + + c = token(CONTIN|KEYWORD|ALIAS); + /* A {...} can be used instead of in...esac for case statements */ + if (c == IN) + c = ESAC; + else if (c == '{') + c = '}'; + else + syntaxerr(NULL); + t = tl = NULL; + while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */ + struct op *tc = casepart(c); + if (tl == NULL) + t = tl = tc, tl->right = NULL; + else + tl->right = tc, tl = tc; + } + musthave(c, KEYWORD|ALIAS); + return (t); +} + +static struct op * +casepart(int endtok) +{ + struct op *t; + XPtrV ptns; + + XPinit(ptns, 16); + t = newtp(TPAT); + /* no ALIAS here */ + if (token(CONTIN | KEYWORD) != '(') + REJECT; + do { + musthave(LWORD, 0); + XPput(ptns, yylval.cp); + } while (token(0) == '|'); + REJECT; + XPput(ptns, NULL); + t->vars = (char **) XPclose(ptns); + musthave(')', 0); + + t->left = c_list(true); + /* Note: POSIX requires the ;; */ + if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) + musthave(BREAK, CONTIN|KEYWORD|ALIAS); + return (t); +} + +static struct op * +function_body(char *name, + bool ksh_func) /* function foo { ... } vs foo() { .. } */ +{ + char *sname, *p; + struct op *t; + bool old_func_parse; + + sname = wdstrip(name, false, false); + /* Check for valid characters in name. POSIX and AT&T ksh93 say only + * allow [a-zA-Z_0-9] but this allows more as old pdkshs have + * allowed more (the following were never allowed: + * NUL TAB NL SP " $ & ' ( ) ; < = > \ ` | + * C_QUOTE covers all but adds # * ? [ ] + */ + for (p = sname; *p; p++) + if (ctype(*p, C_QUOTE)) + yyerror("%s: invalid function name\n", sname); + + /* Note that POSIX allows only compound statements after foo(), sh and + * AT&T ksh allow any command, go with the later since it shouldn't + * break anything. However, for function foo, AT&T ksh only accepts + * an open-brace. + */ + if (ksh_func) { + if (tpeek(CONTIN|KEYWORD|ALIAS) == '(' /* ) */) { + struct tbl *tp; + + /* function foo () { */ + ACCEPT; + musthave(')', 0); + /* degrade to POSIX function */ + ksh_func = false; + if ((tp = ktsearch(&aliases, sname, hash(sname)))) + ktdelete(tp); + } + musthave('{', CONTIN|KEYWORD|ALIAS); /* } */ + REJECT; + } + + t = newtp(TFUNCT); + t->str = sname; + t->u.ksh_func = ksh_func; + t->lineno = source->line; + + old_func_parse = e->flags & EF_FUNC_PARSE; + e->flags |= EF_FUNC_PARSE; + if ((t->left = get_command(CONTIN)) == NULL) { + char *tv; + /* + * Probably something like foo() followed by eof or ;. + * This is accepted by sh and ksh88. + * To make "typeset -f foo" work reliably (so its output can + * be used as input), we pretend there is a colon here. + */ + t->left = newtp(TCOM); + t->left->args = alloc(2 * sizeof(char *), ATEMP); + t->left->args[0] = tv = alloc(3, ATEMP); + tv[0] = CHAR; + tv[1] = ':'; + tv[2] = EOS; + t->left->args[1] = NULL; + t->left->vars = alloc(sizeof(char *), ATEMP); + t->left->vars[0] = NULL; + t->left->lineno = 1; + } + if (!old_func_parse) + e->flags &= ~EF_FUNC_PARSE; + + return (t); +} + +static char ** +wordlist(void) +{ + int c; + XPtrV args; + + XPinit(args, 16); + /* POSIX does not do alias expansion here... */ + if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) { + if (c != ';') /* non-POSIX, but AT&T ksh accepts a ; here */ + REJECT; + return (NULL); + } + while ((c = token(0)) == LWORD) + XPput(args, yylval.cp); + if (c != '\n' && c != ';') + syntaxerr(NULL); + if (XPsize(args) == 0) { + XPfree(args); + return (NULL); + } else { + XPput(args, NULL); + return ((char **)XPclose(args)); + } +} + +/* + * supporting functions + */ + +static struct op * +block(int type, struct op *t1, struct op *t2, char **wp) +{ + struct op *t; + + t = newtp(type); + t->left = t1; + t->right = t2; + t->vars = wp; + return (t); +} + +const struct tokeninfo { + const char *name; + short val; + short reserved; +} tokentab[] = { + /* Reserved words */ + { "if", IF, true }, + { "then", THEN, true }, + { "else", ELSE, true }, + { "elif", ELIF, true }, + { "fi", FI, true }, + { "case", CASE, true }, + { "esac", ESAC, true }, + { "for", FOR, true }, + { "select", SELECT, true }, + { "while", WHILE, true }, + { "until", UNTIL, true }, + { "do", DO, true }, + { "done", DONE, true }, + { "in", IN, true }, + { "function", FUNCTION, true }, + { "time", TIME, true }, + { "{", '{', true }, + { "}", '}', true }, + { "!", BANG, true }, + { "[[", DBRACKET, true }, + /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */ + { "&&", LOGAND, false }, + { "||", LOGOR, false }, + { ";;", BREAK, false }, + { "((", MDPAREN, false }, + { "|&", COPROC, false }, + /* and some special cases... */ + { "newline", '\n', false }, + { NULL, 0, false } +}; + +void +initkeywords(void) +{ + struct tokeninfo const *tt; + struct tbl *p; + + ktinit(&keywords, APERM, + /* must be 80% of 2^n (currently 20 keywords) */ 32); + for (tt = tokentab; tt->name; tt++) { + if (tt->reserved) { + p = ktenter(&keywords, tt->name, hash(tt->name)); + p->flag |= DEFINED|ISSET; + p->type = CKEYWD; + p->val.i = tt->val; + } + } +} + +static void +syntaxerr(const char *what) +{ + char redir[6]; /* 2<<- is the longest redirection, I think */ + const char *s; + struct tokeninfo const *tt; + int c; + + if (!what) + what = "unexpected"; + REJECT; + c = token(0); + Again: + switch (c) { + case 0: + if (nesting.start_token) { + c = nesting.start_token; + source->errline = nesting.start_line; + what = "unmatched"; + goto Again; + } + /* don't quote the EOF */ + yyerror("%s: unexpected EOF\n", T_synerr); + /* NOTREACHED */ + + case LWORD: + s = snptreef(NULL, 32, "%S", yylval.cp); + break; + + case REDIR: + s = snptreef(redir, sizeof(redir), "%R", yylval.iop); + break; + + default: + for (tt = tokentab; tt->name; tt++) + if (tt->val == c) + break; + if (tt->name) + s = tt->name; + else { + if (c > 0 && c < 256) { + redir[0] = c; + redir[1] = '\0'; + } else + shf_snprintf(redir, sizeof(redir), + "?%d", c); + s = redir; + } + } + yyerror("%s: '%s' %s\n", T_synerr, s, what); +} + +static void +nesting_push(struct nesting_state *save, int tok) +{ + *save = nesting; + nesting.start_token = tok; + nesting.start_line = source->line; +} + +static void +nesting_pop(struct nesting_state *saved) +{ + nesting = *saved; +} + +static struct op * +newtp(int type) +{ + struct op *t; + + t = alloc(sizeof(struct op), ATEMP); + t->type = type; + t->u.evalflags = 0; + t->args = NULL; + t->vars = NULL; + t->ioact = NULL; + t->left = t->right = NULL; + t->str = NULL; + return (t); +} + +struct op * +compile(Source *s) +{ + nesting.start_token = 0; + nesting.start_line = 0; + herep = heres; + source = s; + yyparse(); + return (outtree); +} + +/* This kludge exists to take care of sh/AT&T ksh oddity in which + * the arguments of alias/export/readonly/typeset have no field + * splitting, file globbing, or (normal) tilde expansion done. + * AT&T ksh seems to do something similar to this since + * $ touch a=a; typeset a=[ab]; echo "$a" + * a=[ab] + * $ x=typeset; $x a=[ab]; echo "$a" + * a=a + * $ + */ +static int +assign_command(char *s) +{ + if (!*s) + return (0); + return ((strcmp(s, "alias") == 0) || + (strcmp(s, "export") == 0) || + (strcmp(s, "readonly") == 0) || + (strcmp(s, T_typeset) == 0)); +} + +/* Check if we are in the middle of reading an alias */ +static int +inalias(struct source *s) +{ + for (; s && s->type == SALIAS; s = s->next) + if (!(s->flags & SF_ALIASEND)) + return (1); + return (0); +} + + +/* Order important - indexed by Test_meta values + * Note that ||, &&, ( and ) can't appear in as unquoted strings + * in normal shell input, so these can be interpreted unambiguously + * in the evaluation pass. + */ +static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS }; +static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS }; +static const char dbtest_not[] = { CHAR, '!', EOS }; +static const char dbtest_oparen[] = { CHAR, '(', EOS }; +static const char dbtest_cparen[] = { CHAR, ')', EOS }; +const char *const dbtest_tokens[] = { + dbtest_or, dbtest_and, dbtest_not, + dbtest_oparen, dbtest_cparen +}; +const char db_close[] = { CHAR, ']', CHAR, ']', EOS }; +const char db_lthan[] = { CHAR, '<', EOS }; +const char db_gthan[] = { CHAR, '>', EOS }; + +/* + * Test if the current token is a whatever. Accepts the current token if + * it is. Returns 0 if it is not, non-zero if it is (in the case of + * TM_UNOP and TM_BINOP, the returned value is a Test_op). + */ +static Test_op +dbtestp_isa(Test_env *te, Test_meta meta) +{ + int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN)); + int uqword; + char *save = NULL; + Test_op ret = TO_NONOP; + + /* unquoted word? */ + uqword = c == LWORD && *ident; + + if (meta == TM_OR) + ret = c == LOGOR ? TO_NONNULL : TO_NONOP; + else if (meta == TM_AND) + ret = c == LOGAND ? TO_NONNULL : TO_NONOP; + else if (meta == TM_NOT) + ret = (uqword && !strcmp(yylval.cp, + dbtest_tokens[(int)TM_NOT])) ? TO_NONNULL : TO_NONOP; + else if (meta == TM_OPAREN) + ret = c == '(' /*)*/ ? TO_NONNULL : TO_NONOP; + else if (meta == TM_CPAREN) + ret = c == /*(*/ ')' ? TO_NONNULL : TO_NONOP; + else if (meta == TM_UNOP || meta == TM_BINOP) { + if (meta == TM_BINOP && c == REDIR && + (yylval.iop->flag == IOREAD || yylval.iop->flag == IOWRITE)) { + ret = TO_NONNULL; + save = wdcopy(yylval.iop->flag == IOREAD ? + db_lthan : db_gthan, ATEMP); + } else if (uqword && (ret = test_isop(meta, ident))) + save = yylval.cp; + } else /* meta == TM_END */ + ret = (uqword && !strcmp(yylval.cp, + db_close)) ? TO_NONNULL : TO_NONOP; + if (ret != TO_NONOP) { + ACCEPT; + if (meta < NELEM(dbtest_tokens)) + save = wdcopy(dbtest_tokens[(int)meta], ATEMP); + if (save) + XPput(*te->pos.av, save); + } + return (ret); +} + +static const char * +dbtestp_getopnd(Test_env *te, Test_op op MKSH_A_UNUSED, + bool do_eval MKSH_A_UNUSED) +{ + int c = tpeek(ARRAYVAR); + + if (c != LWORD) + return (NULL); + + ACCEPT; + XPput(*te->pos.av, yylval.cp); + + return (null); +} + +static int +dbtestp_eval(Test_env *te MKSH_A_UNUSED, Test_op op MKSH_A_UNUSED, + const char *opnd1 MKSH_A_UNUSED, const char *opnd2 MKSH_A_UNUSED, + bool do_eval MKSH_A_UNUSED) +{ + return (1); +} + +static void +dbtestp_error(Test_env *te, int offset, const char *msg) +{ + te->flags |= TEF_ERROR; + + if (offset < 0) { + REJECT; + /* Kludgy to say the least... */ + symbol = LWORD; + yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) + + offset); + } + syntaxerr(msg); +} diff --git a/mksh/src/tree.c b/mksh/src/tree.c new file mode 100644 index 0000000..aa861db --- /dev/null +++ b/mksh/src/tree.c @@ -0,0 +1,716 @@ +/* $OpenBSD: tree.c,v 1.19 2008/08/11 21:50:35 jaredy Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.30 2010/02/25 20:18:19 tg Exp $"); + +#define INDENT 4 + +#define tputc(c, shf) shf_putchar(c, shf); +static void ptree(struct op *, int, struct shf *); +static void pioact(struct shf *, int, struct ioword *); +static void tputC(int, struct shf *); +static void tputS(char *, struct shf *); +static void vfptreef(struct shf *, int, const char *, va_list); +static struct ioword **iocopy(struct ioword **, Area *); +static void iofree(struct ioword **, Area *); + +/* + * print a command tree + */ +static void +ptree(struct op *t, int indent, struct shf *shf) +{ + const char **w; + struct ioword **ioact; + struct op *t1; + + Chain: + if (t == NULL) + return; + switch (t->type) { + case TCOM: + if (t->vars) + for (w = (const char **)t->vars; *w != NULL; ) + fptreef(shf, indent, "%S ", *w++); + else + shf_puts("#no-vars# ", shf); + if (t->args) + for (w = t->args; *w != NULL; ) + fptreef(shf, indent, "%S ", *w++); + else + shf_puts("#no-args# ", shf); + break; + case TEXEC: + t = t->left; + goto Chain; + case TPAREN: + fptreef(shf, indent + 2, "( %T) ", t->left); + break; + case TPIPE: + fptreef(shf, indent, "%T| ", t->left); + t = t->right; + goto Chain; + case TLIST: + fptreef(shf, indent, "%T%;", t->left); + t = t->right; + goto Chain; + case TOR: + case TAND: + fptreef(shf, indent, "%T%s %T", + t->left, (t->type==TOR) ? "||" : "&&", t->right); + break; + case TBANG: + shf_puts("! ", shf); + t = t->right; + goto Chain; + case TDBRACKET: { + int i; + + shf_puts("[[", shf); + for (i = 0; t->args[i]; i++) + fptreef(shf, indent, " %S", t->args[i]); + shf_puts(" ]] ", shf); + break; + } + case TSELECT: + fptreef(shf, indent, "select %s ", t->str); + /* FALLTHROUGH */ + case TFOR: + if (t->type == TFOR) + fptreef(shf, indent, "for %s ", t->str); + if (t->vars != NULL) { + shf_puts("in ", shf); + for (w = (const char **)t->vars; *w; ) + fptreef(shf, indent, "%S ", *w++); + fptreef(shf, indent, "%;"); + } + fptreef(shf, indent + INDENT, "do%N%T", t->left); + fptreef(shf, indent, "%;done "); + break; + case TCASE: + fptreef(shf, indent, "case %S in", t->str); + for (t1 = t->left; t1 != NULL; t1 = t1->right) { + fptreef(shf, indent, "%N("); + for (w = (const char **)t1->vars; *w != NULL; w++) + fptreef(shf, indent, "%S%c", *w, + (w[1] != NULL) ? '|' : ')'); + fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left); + } + fptreef(shf, indent, "%Nesac "); + break; + case TIF: + case TELIF: + /* 3 == strlen("if ") */ + fptreef(shf, indent + 3, "if %T", t->left); + for (;;) { + t = t->right; + if (t->left != NULL) { + fptreef(shf, indent, "%;"); + fptreef(shf, indent + INDENT, "then%N%T", + t->left); + } + if (t->right == NULL || t->right->type != TELIF) + break; + t = t->right; + fptreef(shf, indent, "%;"); + /* 5 == strlen("elif ") */ + fptreef(shf, indent + 5, "elif %T", t->left); + } + if (t->right != NULL) { + fptreef(shf, indent, "%;"); + fptreef(shf, indent + INDENT, "else%;%T", t->right); + } + fptreef(shf, indent, "%;fi "); + break; + case TWHILE: + case TUNTIL: + /* 6 == strlen("while"/"until") */ + fptreef(shf, indent + 6, "%s %T", + (t->type==TWHILE) ? "while" : "until", + t->left); + fptreef(shf, indent, "%;do"); + fptreef(shf, indent + INDENT, "%;%T", t->right); + fptreef(shf, indent, "%;done "); + break; + case TBRACE: + fptreef(shf, indent + INDENT, "{%;%T", t->left); + fptreef(shf, indent, "%;} "); + break; + case TCOPROC: + fptreef(shf, indent, "%T|& ", t->left); + break; + case TASYNC: + fptreef(shf, indent, "%T& ", t->left); + break; + case TFUNCT: + fptreef(shf, indent, + t->u.ksh_func ? "function %s %T" : "%s() %T", + t->str, t->left); + break; + case TTIME: + fptreef(shf, indent, "time %T", t->left); + break; + default: + shf_puts("<botch>", shf); + break; + } + if ((ioact = t->ioact) != NULL) { + int need_nl = 0; + + while (*ioact != NULL) + pioact(shf, indent, *ioact++); + /* Print here documents after everything else... */ + for (ioact = t->ioact; *ioact != NULL; ) { + struct ioword *iop = *ioact++; + + /* heredoc is 0 when tracing (set -x) */ + if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc && + /* iop->delim[1] == '<' means here string */ + (!iop->delim || iop->delim[1] != '<')) { + tputc('\n', shf); + shf_puts(iop->heredoc, shf); + fptreef(shf, indent, "%s", + evalstr(iop->delim, 0)); + need_nl = 1; + } + } + /* Last delimiter must be followed by a newline (this often + * leads to an extra blank line, but its not worth worrying + * about) + */ + if (need_nl) + tputc('\n', shf); + } +} + +static void +pioact(struct shf *shf, int indent, struct ioword *iop) +{ + int flag = iop->flag; + int type = flag & IOTYPE; + int expected; + + expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 : + (type == IOCAT || type == IOWRITE) ? 1 : + (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit : + iop->unit + 1; + if (iop->unit != expected) + shf_fprintf(shf, "%d", iop->unit); + + switch (type) { + case IOREAD: + shf_puts("< ", shf); + break; + case IOHERE: + shf_puts(flag & IOSKIP ? "<<-" : "<<", shf); + break; + case IOCAT: + shf_puts(">> ", shf); + break; + case IOWRITE: + shf_puts(flag & IOCLOB ? ">| " : "> ", shf); + break; + case IORDWR: + shf_puts("<> ", shf); + break; + case IODUP: + shf_puts(flag & IORDUP ? "<&" : ">&", shf); + break; + } + /* name/delim are 0 when printing syntax errors */ + if (type == IOHERE) { + if (iop->delim) + fptreef(shf, indent, "%s%S ", + /* here string */ iop->delim[1] == '<' ? "" : " ", + iop->delim); + else + tputc(' ', shf); + } else if (iop->name) + fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ", + iop->name); +} + + +/* + * variants of fputc, fputs for ptreef and snptreef + */ +static void +tputC(int c, struct shf *shf) +{ + if ((c&0x60) == 0) { /* C0|C1 */ + tputc((c&0x80) ? '$' : '^', shf); + tputc(((c&0x7F)|0x40), shf); + } else if ((c&0x7F) == 0x7F) { /* DEL */ + tputc((c&0x80) ? '$' : '^', shf); + tputc('?', shf); + } else + tputc(c, shf); +} + +static void +tputS(char *wp, struct shf *shf) +{ + int c, quotelevel = 0; + + /* problems: + * `...` -> $(...) + * 'foo' -> "foo" + * could change encoding to: + * OQUOTE ["'] ... CQUOTE ["'] + * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case) + */ + while (1) + switch (*wp++) { + case EOS: + return; + case ADELIM: + case CHAR: + tputC(*wp++, shf); + break; + case QCHAR: + c = *wp++; + if (!quotelevel || (c == '"' || c == '`' || c == '$')) + tputc('\\', shf); + tputC(c, shf); + break; + case COMSUB: + shf_puts("$(", shf); + while (*wp != 0) + tputC(*wp++, shf); + tputc(')', shf); + wp++; + break; + case EXPRSUB: + shf_puts("$((", shf); + while (*wp != 0) + tputC(*wp++, shf); + shf_puts("))", shf); + wp++; + break; + case OQUOTE: + quotelevel++; + tputc('"', shf); + break; + case CQUOTE: + if (quotelevel) + quotelevel--; + tputc('"', shf); + break; + case OSUBST: + tputc('$', shf); + if (*wp++ == '{') + tputc('{', shf); + while ((c = *wp++) != 0) + tputC(c, shf); + break; + case CSUBST: + if (*wp++ == '}') + tputc('}', shf); + break; + case OPAT: + tputc(*wp++, shf); + tputc('(', shf); + break; + case SPAT: + tputc('|', shf); + break; + case CPAT: + tputc(')', shf); + break; + } +} + +/* + * this is the _only_ way to reliably handle + * variable args with an ANSI compiler + */ +/* VARARGS */ +int +fptreef(struct shf *shf, int indent, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + + vfptreef(shf, indent, fmt, va); + va_end(va); + return (0); +} + +/* VARARGS */ +char * +snptreef(char *s, int n, const char *fmt, ...) +{ + va_list va; + struct shf shf; + + shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf); + + va_start(va, fmt); + vfptreef(&shf, 0, fmt, va); + va_end(va); + + return (shf_sclose(&shf)); /* null terminates */ +} + +static void +vfptreef(struct shf *shf, int indent, const char *fmt, va_list va) +{ + int c; + + while ((c = *fmt++)) { + if (c == '%') { + switch ((c = *fmt++)) { + case 'c': + tputc(va_arg(va, int), shf); + break; + case 's': + shf_puts(va_arg(va, char *), shf); + break; + case 'S': /* word */ + tputS(va_arg(va, char *), shf); + break; + case 'd': /* decimal */ + shf_fprintf(shf, "%d", va_arg(va, int)); + break; + case 'u': /* decimal */ + shf_fprintf(shf, "%u", va_arg(va, unsigned int)); + break; + case 'T': /* format tree */ + ptree(va_arg(va, struct op *), indent, shf); + break; + case ';': /* newline or ; */ + case 'N': /* newline or space */ + if (shf->flags & SHF_STRING) { + if (c == ';') + tputc(';', shf); + tputc(' ', shf); + } else { + int i; + + tputc('\n', shf); + for (i = indent; i >= 8; i -= 8) + tputc('\t', shf); + for (; i > 0; --i) + tputc(' ', shf); + } + break; + case 'R': + pioact(shf, indent, va_arg(va, struct ioword *)); + break; + default: + tputc(c, shf); + break; + } + } else + tputc(c, shf); + } +} + +/* + * copy tree (for function definition) + */ +struct op * +tcopy(struct op *t, Area *ap) +{ + struct op *r; + const char **tw; + char **rw; + + if (t == NULL) + return (NULL); + + r = alloc(sizeof(struct op), ap); + + r->type = t->type; + r->u.evalflags = t->u.evalflags; + + if (t->type == TCASE) + r->str = wdcopy(t->str, ap); + else + strdupx(r->str, t->str, ap); + + if (t->vars == NULL) + r->vars = NULL; + else { + for (tw = (const char **)t->vars; *tw++ != NULL; ) + ; + rw = r->vars = alloc((tw - (const char **)t->vars + 1) * + sizeof(*tw), ap); + for (tw = (const char **)t->vars; *tw != NULL; ) + *rw++ = wdcopy(*tw++, ap); + *rw = NULL; + } + + if (t->args == NULL) + r->args = NULL; + else { + for (tw = t->args; *tw++ != NULL; ) + ; + r->args = (const char **)(rw = alloc((tw - t->args + 1) * + sizeof(*tw), ap)); + for (tw = t->args; *tw != NULL; ) + *rw++ = wdcopy(*tw++, ap); + *rw = NULL; + } + + r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap); + + r->left = tcopy(t->left, ap); + r->right = tcopy(t->right, ap); + r->lineno = t->lineno; + + return (r); +} + +char * +wdcopy(const char *wp, Area *ap) +{ + size_t len = wdscan(wp, EOS) - wp; + return (memcpy(alloc(len, ap), wp, len)); +} + +/* return the position of prefix c in wp plus 1 */ +const char * +wdscan(const char *wp, int c) +{ + int nest = 0; + + while (1) + switch (*wp++) { + case EOS: + return (wp); + case ADELIM: + if (c == ADELIM) + return (wp + 1); + /* FALLTHROUGH */ + case CHAR: + case QCHAR: + wp++; + break; + case COMSUB: + case EXPRSUB: + while (*wp++ != 0) + ; + break; + case OQUOTE: + case CQUOTE: + break; + case OSUBST: + nest++; + while (*wp++ != '\0') + ; + break; + case CSUBST: + wp++; + if (c == CSUBST && nest == 0) + return (wp); + nest--; + break; + case OPAT: + nest++; + wp++; + break; + case SPAT: + case CPAT: + if (c == wp[-1] && nest == 0) + return (wp); + if (wp[-1] == CPAT) + nest--; + break; + default: + internal_warningf( + "wdscan: unknown char 0x%x (carrying on)", + wp[-1]); + } +} + +/* return a copy of wp without any of the mark up characters and + * with quote characters (" ' \) stripped. + * (string is allocated from ATEMP) + */ +char * +wdstrip(const char *wp, bool keepq, bool make_magic) +{ + struct shf shf; + int c; + + shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf); + + /* problems: + * `...` -> $(...) + * x${foo:-"hi"} -> x${foo:-hi} + * x${foo:-'hi'} -> x${foo:-hi} unless keepq + */ + while (1) + switch (*wp++) { + case EOS: + return (shf_sclose(&shf)); /* null terminates */ + case ADELIM: + case CHAR: + c = *wp++; + if (make_magic && (ISMAGIC(c) || c == '[' || c == NOT || + c == '-' || c == ']' || c == '*' || c == '?')) + shf_putchar(MAGIC, &shf); + shf_putchar(c, &shf); + break; + case QCHAR: + c = *wp++; + if (keepq && (c == '"' || c == '`' || c == '$' || c == '\\')) + shf_putchar('\\', &shf); + shf_putchar(c, &shf); + break; + case COMSUB: + shf_puts("$(", &shf); + while (*wp != 0) + shf_putchar(*wp++, &shf); + shf_putchar(')', &shf); + break; + case EXPRSUB: + shf_puts("$((", &shf); + while (*wp != 0) + shf_putchar(*wp++, &shf); + shf_puts("))", &shf); + break; + case OQUOTE: + break; + case CQUOTE: + break; + case OSUBST: + shf_putchar('$', &shf); + if (*wp++ == '{') + shf_putchar('{', &shf); + while ((c = *wp++) != 0) + shf_putchar(c, &shf); + break; + case CSUBST: + if (*wp++ == '}') + shf_putchar('}', &shf); + break; + case OPAT: + if (make_magic) { + shf_putchar(MAGIC, &shf); + shf_putchar(*wp++ | 0x80, &shf); + } else { + shf_putchar(*wp++, &shf); + shf_putchar('(', &shf); + } + break; + case SPAT: + if (make_magic) + shf_putchar(MAGIC, &shf); + shf_putchar('|', &shf); + break; + case CPAT: + if (make_magic) + shf_putchar(MAGIC, &shf); + shf_putchar(')', &shf); + break; + } +} + +static struct ioword ** +iocopy(struct ioword **iow, Area *ap) +{ + struct ioword **ior; + int i; + + for (ior = iow; *ior++ != NULL; ) + ; + ior = alloc((ior - iow + 1) * sizeof(struct ioword *), ap); + + for (i = 0; iow[i] != NULL; i++) { + struct ioword *p, *q; + + p = iow[i]; + q = alloc(sizeof(struct ioword), ap); + ior[i] = q; + *q = *p; + if (p->name != NULL) + q->name = wdcopy(p->name, ap); + if (p->delim != NULL) + q->delim = wdcopy(p->delim, ap); + if (p->heredoc != NULL) + strdupx(q->heredoc, p->heredoc, ap); + } + ior[i] = NULL; + + return (ior); +} + +/* + * free tree (for function definition) + */ +void +tfree(struct op *t, Area *ap) +{ + char **w; + + if (t == NULL) + return; + + if (t->str != NULL) + afree(t->str, ap); + + if (t->vars != NULL) { + for (w = t->vars; *w != NULL; w++) + afree(*w, ap); + afree(t->vars, ap); + } + + if (t->args != NULL) { + union mksh_ccphack cw; + /* XXX we assume the caller is right */ + cw.ro = t->args; + for (w = cw.rw; *w != NULL; w++) + afree(*w, ap); + afree(t->args, ap); + } + + if (t->ioact != NULL) + iofree(t->ioact, ap); + + tfree(t->left, ap); + tfree(t->right, ap); + + afree(t, ap); +} + +static void +iofree(struct ioword **iow, Area *ap) +{ + struct ioword **iop; + struct ioword *p; + + for (iop = iow; (p = *iop++) != NULL; ) { + if (p->name != NULL) + afree(p->name, ap); + if (p->delim != NULL) + afree(p->delim, ap); + if (p->heredoc != NULL) + afree(p->heredoc, ap); + afree(p, ap); + } + afree(iow, ap); +} diff --git a/mksh/src/var.c b/mksh/src/var.c new file mode 100644 index 0000000..4e9729e --- /dev/null +++ b/mksh/src/var.c @@ -0,0 +1,1490 @@ +/* $OpenBSD: var.c,v 1.34 2007/10/15 02:16:35 deraadt Exp $ */ + +/*- + * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 + * Thorsten Glaser <tg@mirbsd.org> + * + * Provided that these terms and disclaimer and all copyright notices + * are retained or reproduced in an accompanying document, permission + * is granted to deal in this work without restriction, including un- + * limited rights to use, publicly perform, distribute, sell, modify, + * merge, give away, or sublicence. + * + * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to + * the utmost extent permitted by applicable law, neither express nor + * implied; without malicious intent or gross negligence. In no event + * may a licensor, author or contributor be held liable for indirect, + * direct, other damage, loss, or other issues arising in any way out + * of dealing in the work, even if advised of the possibility of such + * damage or existence of a defect, except proven that it results out + * of said person's immediate fault when using the work as intended. + */ + +#include "sh.h" + +#if defined(__OpenBSD__) +#include <sys/sysctl.h> +#endif + +__RCSID("$MirOS: src/bin/mksh/var.c,v 1.110 2010/07/25 11:35:43 tg Exp $"); + +/* + * Variables + * + * WARNING: unreadable code, needs a rewrite + * + * if (flag&INTEGER), val.i contains integer value, and type contains base. + * otherwise, (val.s + type) contains string value. + * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting. + */ +static struct tbl vtemp; +static struct table specials; +static char *formatstr(struct tbl *, const char *); +static void exportprep(struct tbl *, const char *); +static int special(const char *); +static void unspecial(const char *); +static void getspec(struct tbl *); +static void setspec(struct tbl *); +static void unsetspec(struct tbl *); +static int getint(struct tbl *, mksh_ari_t *, bool); +static mksh_ari_t intval(struct tbl *); +static struct tbl *arraysearch(struct tbl *, uint32_t); +static const char *array_index_calc(const char *, bool *, uint32_t *); +static uint32_t oaathash_update(register uint32_t, register const uint8_t *, + register size_t); +static uint32_t oaathash_finalise(register uint32_t); + +uint8_t set_refflag = 0; + +/* + * create a new block for function calls and simple commands + * assume caller has allocated and set up e->loc + */ +void +newblock(void) +{ + struct block *l; + static const char *empty[] = { null }; + + l = alloc(sizeof(struct block), ATEMP); + l->flags = 0; + ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */ + if (!e->loc) { + l->argc = 0; + l->argv = empty; + } else { + l->argc = e->loc->argc; + l->argv = e->loc->argv; + } + l->exit = l->error = NULL; + ktinit(&l->vars, &l->area, 0); + ktinit(&l->funs, &l->area, 0); + l->next = e->loc; + e->loc = l; +} + +/* + * pop a block handling special variables + */ +void +popblock(void) +{ + struct block *l = e->loc; + struct tbl *vp, **vpp = l->vars.tbls, *vq; + int i; + + e->loc = l->next; /* pop block */ + for (i = l->vars.size; --i >= 0; ) + if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) { + if ((vq = global(vp->name))->flag & ISSET) + setspec(vq); + else + unsetspec(vq); + } + if (l->flags & BF_DOGETOPTS) + user_opt = l->getopts_state; + afreeall(&l->area); + afree(l, ATEMP); +} + +/* called by main() to initialise variable data structures */ +#define VARSPEC_DEFNS +#include "var_spec.h" + +enum var_specs { +#define VARSPEC_ENUMS +#include "var_spec.h" + V_MAX +}; + +static const char * const initvar_names[] = { +#define VARSPEC_ITEMS +#include "var_spec.h" +}; + +void +initvar(void) +{ + int i = 0; + struct tbl *tp; + + ktinit(&specials, APERM, + /* must be 80% of 2^n (currently 12 specials) */ 16); + while (i < V_MAX - 1) { + tp = ktenter(&specials, initvar_names[i], + hash(initvar_names[i])); + tp->flag = DEFINED|ISSET; + tp->type = ++i; + } +} + +/* Used to calculate an array index for global()/local(). Sets *arrayp to + * true if this is an array, sets *valp to the array index, returns + * the basename of the array. + */ +static const char * +array_index_calc(const char *n, bool *arrayp, uint32_t *valp) +{ + const char *p; + int len; + char *ap = NULL; + + *arrayp = false; + redo_from_ref: + p = skip_varname(n, false); + if (!set_refflag && (p != n) && ksh_isalphx(n[0])) { + struct block *l = e->loc; + struct tbl *vp; + char *vn; + uint32_t h; + + strndupx(vn, n, p - n, ATEMP); + h = hash(vn); + /* check if this is a reference */ + do { + vp = ktsearch(&l->vars, vn, h); + } while (!vp && (l = l->next)); + afree(vn, ATEMP); + if (vp && (vp->flag & (DEFINED|ASSOC|ARRAY)) == + (DEFINED|ASSOC)) { + char *cp; + + /* gotcha! */ + cp = shf_smprintf("%s%s", str_val(vp), p); + afree(ap, ATEMP); + n = ap = cp; + goto redo_from_ref; + } + } + + if (p != n && *p == '[' && (len = array_ref_len(p))) { + char *sub, *tmp; + mksh_ari_t rval; + + /* Calculate the value of the subscript */ + *arrayp = true; + strndupx(tmp, p + 1, len - 2, ATEMP); + sub = substitute(tmp, 0); + afree(tmp, ATEMP); + strndupx(n, n, p - n, ATEMP); + evaluate(sub, &rval, KSH_UNWIND_ERROR, true); + *valp = (uint32_t)rval; + afree(sub, ATEMP); + } + return (n); +} + +/* + * Search for variable, if not found create globally. + */ +struct tbl * +global(const char *n) +{ + struct block *l = e->loc; + struct tbl *vp; + int c; + bool array; + uint32_t h, val; + + /* Check to see if this is an array */ + n = array_index_calc(n, &array, &val); + h = hash(n); + c = n[0]; + if (!ksh_isalphx(c)) { + if (array) + errorf("bad substitution"); + vp = &vtemp; + vp->flag = DEFINED; + vp->type = 0; + vp->areap = ATEMP; + *vp->name = c; + if (ksh_isdigit(c)) { + for (c = 0; ksh_isdigit(*n); n++) + c = c*10 + *n-'0'; + if (c <= l->argc) + /* setstr can't fail here */ + setstr(vp, l->argv[c], KSH_RETURN_ERROR); + vp->flag |= RDONLY; + return (vp); + } + vp->flag |= RDONLY; + if (n[1] != '\0') + return (vp); + vp->flag |= ISSET|INTEGER; + switch (c) { + case '$': + vp->val.i = kshpid; + break; + case '!': + /* If no job, expand to nothing */ + if ((vp->val.i = j_async()) == 0) + vp->flag &= ~(ISSET|INTEGER); + break; + case '?': + vp->val.i = exstat; + break; + case '#': + vp->val.i = l->argc; + break; + case '-': + vp->flag &= ~INTEGER; + vp->val.s = getoptions(); + break; + default: + vp->flag &= ~(ISSET|INTEGER); + } + return (vp); + } + for (l = e->loc; ; l = l->next) { + vp = ktsearch(&l->vars, n, h); + if (vp != NULL) { + if (array) + return (arraysearch(vp, val)); + else + return (vp); + } + if (l->next == NULL) + break; + } + vp = ktenter(&l->vars, n, h); + if (array) + vp = arraysearch(vp, val); + vp->flag |= DEFINED; + if (special(n)) + vp->flag |= SPECIAL; + return (vp); +} + +/* + * Search for local variable, if not found create locally. + */ +struct tbl * +local(const char *n, bool copy) +{ + struct block *l = e->loc; + struct tbl *vp; + bool array; + uint32_t h, val; + + /* Check to see if this is an array */ + n = array_index_calc(n, &array, &val); + h = hash(n); + if (!ksh_isalphx(*n)) { + vp = &vtemp; + vp->flag = DEFINED|RDONLY; + vp->type = 0; + vp->areap = ATEMP; + return (vp); + } + vp = ktenter(&l->vars, n, h); + if (copy && !(vp->flag & DEFINED)) { + struct block *ll = l; + struct tbl *vq = NULL; + + while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h))) + ; + if (vq) { + vp->flag |= vq->flag & + (EXPORT | INTEGER | RDONLY | LJUST | RJUST | + ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L); + if (vq->flag & INTEGER) + vp->type = vq->type; + vp->u2.field = vq->u2.field; + } + } + if (array) + vp = arraysearch(vp, val); + vp->flag |= DEFINED; + if (special(n)) + vp->flag |= SPECIAL; + return (vp); +} + +/* get variable string value */ +char * +str_val(struct tbl *vp) +{ + char *s; + + if ((vp->flag&SPECIAL)) + getspec(vp); + if (!(vp->flag&ISSET)) + s = null; /* special to dollar() */ + else if (!(vp->flag&INTEGER)) /* string source */ + s = vp->val.s + vp->type; + else { /* integer source */ + /* worst case number length is when base=2 */ + /* 1 (minus) + 2 (base, up to 36) + 1 ('#') + number of bits + * in the mksh_uari_t + 1 (NUL) */ + char strbuf[1 + 2 + 1 + 8 * sizeof(mksh_uari_t) + 1]; + const char *digits = (vp->flag & UCASEV_AL) ? + digits_uc : digits_lc; + mksh_uari_t n; + int base; + + s = strbuf + sizeof(strbuf); + if (vp->flag & INT_U) + n = vp->val.u; + else + n = (vp->val.i < 0) ? -vp->val.i : vp->val.i; + base = (vp->type == 0) ? 10 : vp->type; + + if (base == 1) { + size_t sz = 1; + + *(s = strbuf) = '1'; + s[1] = '#'; + if (!UTFMODE || ((n & 0xFF80) == 0xEF80)) + /* OPTU-16 -> raw octet */ + s[2] = n & 0xFF; + else + sz = utf_wctomb(s + 2, n); + s[2 + sz] = '\0'; + } else { + *--s = '\0'; + do { + *--s = digits[n % base]; + n /= base; + } while (n != 0); + if (base != 10) { + *--s = '#'; + *--s = digits[base % 10]; + if (base >= 10) + *--s = digits[base / 10]; + } + if (!(vp->flag & INT_U) && vp->val.i < 0) + *--s = '-'; + } + if (vp->flag & (RJUST|LJUST)) /* case already dealt with */ + s = formatstr(vp, s); + else + strdupx(s, s, ATEMP); + } + return (s); +} + +/* get variable integer value, with error checking */ +static mksh_ari_t +intval(struct tbl *vp) +{ + mksh_ari_t num; + int base; + + base = getint(vp, &num, false); + if (base == -1) + /* XXX check calls - is error here ok by POSIX? */ + errorf("%s: bad number", str_val(vp)); + return (num); +} + +/* set variable to string value */ +int +setstr(struct tbl *vq, const char *s, int error_ok) +{ + char *salloc = NULL; + int no_ro_check = error_ok & 0x4; + + error_ok &= ~0x4; + if ((vq->flag & RDONLY) && !no_ro_check) { + warningf(true, "%s: is read only", vq->name); + if (!error_ok) + errorfz(); + return (0); + } + if (!(vq->flag&INTEGER)) { /* string dest */ + if ((vq->flag&ALLOC)) { + /* debugging */ + if (s >= vq->val.s && + s <= vq->val.s + strlen(vq->val.s)) + internal_errorf( + "setstr: %s=%s: assigning to self", + vq->name, s); + afree(vq->val.s, vq->areap); + } + vq->flag &= ~(ISSET|ALLOC); + vq->type = 0; + if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST))) + s = salloc = formatstr(vq, s); + if ((vq->flag&EXPORT)) + exportprep(vq, s); + else { + strdupx(vq->val.s, s, vq->areap); + vq->flag |= ALLOC; + } + } else { /* integer dest */ + if (!v_evaluate(vq, s, error_ok, true)) + return (0); + } + vq->flag |= ISSET; + if ((vq->flag&SPECIAL)) + setspec(vq); + afree(salloc, ATEMP); + return (1); +} + +/* set variable to integer */ +void +setint(struct tbl *vq, mksh_ari_t n) +{ + if (!(vq->flag&INTEGER)) { + struct tbl *vp = &vtemp; + vp->flag = (ISSET|INTEGER); + vp->type = 0; + vp->areap = ATEMP; + vp->val.i = n; + /* setstr can't fail here */ + setstr(vq, str_val(vp), KSH_RETURN_ERROR); + } else + vq->val.i = n; + vq->flag |= ISSET; + if ((vq->flag&SPECIAL)) + setspec(vq); +} + +static int +getint(struct tbl *vp, mksh_ari_t *nump, bool arith) +{ + char *s; + int c, base, neg; + bool have_base = false; + mksh_ari_t num; + + if (vp->flag&SPECIAL) + getspec(vp); + /* XXX is it possible for ISSET to be set and val.s to be 0? */ + if (!(vp->flag&ISSET) || (!(vp->flag&INTEGER) && vp->val.s == NULL)) + return (-1); + if (vp->flag&INTEGER) { + *nump = vp->val.i; + return (vp->type); + } + s = vp->val.s + vp->type; + if (s == NULL) /* redundant given initial test */ + s = null; + base = 10; + num = 0; + neg = 0; + if (arith && *s == '0' && *(s+1)) { + s++; + if (*s == 'x' || *s == 'X') { + s++; + base = 16; + } else if (vp->flag & ZEROFIL) { + while (*s == '0') + s++; + } else + base = 8; + have_base = true; + } + for (c = *s++; c ; c = *s++) { + if (c == '-') { + neg++; + continue; + } else if (c == '#') { + base = (int)num; + if (have_base || base < 1 || base > 36) + return (-1); + if (base == 1) { + unsigned int wc; + + if (!UTFMODE) + wc = *(unsigned char *)s; + else if (utf_mbtowc(&wc, s) == (size_t)-1) + /* OPTU-8 -> OPTU-16 */ + /* + * (with a twist: 1#\uEF80 converts + * the same as 1#\x80 does, thus is + * not round-tripping correctly XXX) + */ + wc = 0xEF00 + *(unsigned char *)s; + *nump = (mksh_ari_t)wc; + return (1); + } + num = 0; + have_base = true; + continue; + } else if (ksh_isdigit(c)) + c -= '0'; + else if (ksh_islower(c)) + c -= 'a' - 10; + else if (ksh_isupper(c)) + c -= 'A' - 10; + else + return (-1); + if (c < 0 || c >= base) + return (-1); + num = num * base + c; + } + if (neg) + num = -num; + *nump = num; + return (base); +} + +/* convert variable vq to integer variable, setting its value from vp + * (vq and vp may be the same) + */ +struct tbl * +setint_v(struct tbl *vq, struct tbl *vp, bool arith) +{ + int base; + mksh_ari_t num; + + if ((base = getint(vp, &num, arith)) == -1) + return (NULL); + if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) { + vq->flag &= ~ALLOC; + afree(vq->val.s, vq->areap); + } + vq->val.i = num; + if (vq->type == 0) /* default base */ + vq->type = base; + vq->flag |= ISSET|INTEGER; + if (vq->flag&SPECIAL) + setspec(vq); + return (vq); +} + +static char * +formatstr(struct tbl *vp, const char *s) +{ + int olen, nlen; + char *p, *q; + size_t psiz; + + olen = utf_mbswidth(s); + + if (vp->flag & (RJUST|LJUST)) { + if (!vp->u2.field) /* default field width */ + vp->u2.field = olen; + nlen = vp->u2.field; + } else + nlen = olen; + + p = alloc((psiz = nlen * /* MB_LEN_MAX */ 3 + 1), ATEMP); + if (vp->flag & (RJUST|LJUST)) { + int slen = olen, i = 0; + + if (vp->flag & RJUST) { + const char *qq = s; + int n = 0; + + while (i < slen) + i += utf_widthadj(qq, &qq); + /* strip trailing spaces (AT&T uses qq[-1] == ' ') */ + while (qq > s && ksh_isspace(qq[-1])) { + --qq; + --slen; + } + if (vp->flag & ZEROFIL && vp->flag & INTEGER) { + if (s[1] == '#') + n = 2; + else if (s[2] == '#') + n = 3; + if (vp->u2.field <= n) + n = 0; + } + if (n) { + memcpy(p, s, n); + s += n; + } + while (slen > vp->u2.field) + slen -= utf_widthadj(s, &s); + if (vp->u2.field - slen) + memset(p + n, (vp->flag & ZEROFIL) ? '0' : ' ', + vp->u2.field - slen); + slen -= n; + shf_snprintf(p + vp->u2.field - slen, + psiz - (vp->u2.field - slen), + "%.*s", slen, s); + } else { + /* strip leading spaces/zeros */ + while (ksh_isspace(*s)) + s++; + if (vp->flag & ZEROFIL) + while (*s == '0') + s++; + shf_snprintf(p, nlen + 1, "%-*.*s", + vp->u2.field, vp->u2.field, s); + } + } else + memcpy(p, s, strlen(s) + 1); + + if (vp->flag & UCASEV_AL) { + for (q = p; *q; q++) + *q = ksh_toupper(*q); + } else if (vp->flag & LCASEV) { + for (q = p; *q; q++) + *q = ksh_tolower(*q); + } + + return (p); +} + +/* + * make vp->val.s be "name=value" for quick exporting. + */ +static void +exportprep(struct tbl *vp, const char *val) +{ + char *xp; + char *op = (vp->flag&ALLOC) ? vp->val.s : NULL; + int namelen = strlen(vp->name); + int vallen = strlen(val) + 1; + + vp->flag |= ALLOC; + xp = alloc(namelen + 1 + vallen, vp->areap); + memcpy(vp->val.s = xp, vp->name, namelen); + xp += namelen; + *xp++ = '='; + vp->type = xp - vp->val.s; /* offset to value */ + memcpy(xp, val, vallen); + if (op != NULL) + afree(op, vp->areap); +} + +/* + * lookup variable (according to (set&LOCAL)), + * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, + * LCASEV, UCASEV_AL), and optionally set its value if an assignment. + */ +struct tbl * +typeset(const char *var, Tflag set, Tflag clr, int field, int base) +{ + struct tbl *vp; + struct tbl *vpbase, *t; + char *tvar; + const char *val; + int len; + + /* check for valid variable name, search for value */ + val = skip_varname(var, false); + if (val == var) + return (NULL); + mkssert(var != NULL); + mkssert(*var != 0); + if (*val == '[') { + if (set_refflag) + errorf("%s: reference variable cannot be an array", + var); + len = array_ref_len(val); + if (len == 0) + return (NULL); + /* IMPORT is only used when the shell starts up and is + * setting up its environment. Allow only simple array + * references at this time since parameter/command substitution + * is preformed on the [expression] which would be a major + * security hole. + */ + if (set & IMPORT) { + int i; + for (i = 1; i < len - 1; i++) + if (!ksh_isdigit(val[i])) + return (NULL); + } + val += len; + } + if (*val == '=') + strndupx(tvar, var, val++ - var, ATEMP); + else { + /* Importing from original environment: must have an = */ + if (set & IMPORT) + return (NULL); + strdupx(tvar, var, ATEMP); + val = NULL; + /* handle foo[*] ⇒ foo (whole array) mapping for R39b */ + len = strlen(tvar); + if (len > 3 && tvar[len-3] == '[' && tvar[len-2] == '*' && + tvar[len-1] == ']') + tvar[len-3] = '\0'; + } + + /* Prevent typeset from creating a local PATH/ENV/SHELL */ + if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 || + strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0)) + errorf("%s: restricted", tvar); + + vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) : + global(tvar); + if (set_refflag == 2 && (vp->flag & (ARRAY|ASSOC)) == ASSOC) + vp->flag &= ~ASSOC; + else if (set_refflag == 1) { + if (vp->flag & ARRAY) { + struct tbl *a, *tmp; + + /* Free up entire array */ + for (a = vp->u.array; a; ) { + tmp = a; + a = a->u.array; + if (tmp->flag & ALLOC) + afree(tmp->val.s, tmp->areap); + afree(tmp, tmp->areap); + } + vp->u.array = NULL; + vp->flag &= ~ARRAY; + } + vp->flag |= ASSOC; + } + + set &= ~(LOCAL|LOCAL_COPY); + + vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp; + + /* only allow export flag to be set. AT&T ksh allows any attribute to + * be changed which means it can be truncated or modified (-L/-R/-Z/-i) + */ + if ((vpbase->flag&RDONLY) && + (val || clr || (set & ~EXPORT))) + /* XXX check calls - is error here ok by POSIX? */ + errorf("%s: is read only", tvar); + afree(tvar, ATEMP); + + /* most calls are with set/clr == 0 */ + if (set | clr) { + bool ok = true; + + /* XXX if x[0] isn't set, there will be problems: need to have + * one copy of attributes for arrays... + */ + for (t = vpbase; t; t = t->u.array) { + bool fake_assign; + char *s = NULL; + char *free_me = NULL; + + fake_assign = (t->flag & ISSET) && (!val || t != vp) && + ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) || + ((t->flag & INTEGER) && (clr & INTEGER)) || + (!(t->flag & INTEGER) && (set & INTEGER))); + if (fake_assign) { + if (t->flag & INTEGER) { + s = str_val(t); + free_me = NULL; + } else { + s = t->val.s + t->type; + free_me = (t->flag & ALLOC) ? t->val.s : + NULL; + } + t->flag &= ~ALLOC; + } + if (!(t->flag & INTEGER) && (set & INTEGER)) { + t->type = 0; + t->flag &= ~ALLOC; + } + t->flag = (t->flag | set) & ~clr; + /* Don't change base if assignment is to be done, + * in case assignment fails. + */ + if ((set & INTEGER) && base > 0 && (!val || t != vp)) + t->type = base; + if (set & (LJUST|RJUST|ZEROFIL)) + t->u2.field = field; + if (fake_assign) { + if (!setstr(t, s, KSH_RETURN_ERROR)) { + /* Somewhat arbitrary action here: + * zap contents of variable, but keep + * the flag settings. + */ + ok = false; + if (t->flag & INTEGER) + t->flag &= ~ISSET; + else { + if (t->flag & ALLOC) + afree(t->val.s, t->areap); + t->flag &= ~(ISSET|ALLOC); + t->type = 0; + } + } + if (free_me) + afree(free_me, t->areap); + } + } + if (!ok) + errorfz(); + } + + if (val != NULL) { + if (vp->flag&INTEGER) { + /* do not zero base before assignment */ + setstr(vp, val, KSH_UNWIND_ERROR | 0x4); + /* Done after assignment to override default */ + if (base > 0) + vp->type = base; + } else + /* setstr can't fail (readonly check already done) */ + setstr(vp, val, KSH_RETURN_ERROR | 0x4); + } + + /* only x[0] is ever exported, so use vpbase */ + if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) && + vpbase->type == 0) + exportprep(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null); + + return (vp); +} + +/** + * Unset a variable. The flags can be: + * |1 = tear down entire array + * |2 = keep attributes, only unset content + */ +void +unset(struct tbl *vp, int flags) +{ + if (vp->flag & ALLOC) + afree(vp->val.s, vp->areap); + if ((vp->flag & ARRAY) && (flags & 1)) { + struct tbl *a, *tmp; + + /* Free up entire array */ + for (a = vp->u.array; a; ) { + tmp = a; + a = a->u.array; + if (tmp->flag & ALLOC) + afree(tmp->val.s, tmp->areap); + afree(tmp, tmp->areap); + } + vp->u.array = NULL; + } + if (flags & 2) { + vp->flag &= ~(ALLOC|ISSET); + return; + } + /* If foo[0] is being unset, the remainder of the array is kept... */ + vp->flag &= SPECIAL | ((flags & 1) ? 0 : ARRAY|DEFINED); + if (vp->flag & SPECIAL) + unsetspec(vp); /* responsible for 'unspecial'ing var */ +} + +/* return a pointer to the first char past a legal variable name (returns the + * argument if there is no legal name, returns a pointer to the terminating + * NUL if whole string is legal). + */ +const char * +skip_varname(const char *s, int aok) +{ + int alen; + + if (s && ksh_isalphx(*s)) { + while (*++s && ksh_isalnux(*s)) + ; + if (aok && *s == '[' && (alen = array_ref_len(s))) + s += alen; + } + return (s); +} + +/* Return a pointer to the first character past any legal variable name */ +const char * +skip_wdvarname(const char *s, + int aok) /* skip array de-reference? */ +{ + if (s[0] == CHAR && ksh_isalphx(s[1])) { + do { + s += 2; + } while (s[0] == CHAR && ksh_isalnux(s[1])); + if (aok && s[0] == CHAR && s[1] == '[') { + /* skip possible array de-reference */ + const char *p = s; + char c; + int depth = 0; + + while (1) { + if (p[0] != CHAR) + break; + c = p[1]; + p += 2; + if (c == '[') + depth++; + else if (c == ']' && --depth == 0) { + s = p; + break; + } + } + } + } + return (s); +} + +/* Check if coded string s is a variable name */ +int +is_wdvarname(const char *s, int aok) +{ + const char *p = skip_wdvarname(s, aok); + + return (p != s && p[0] == EOS); +} + +/* Check if coded string s is a variable assignment */ +int +is_wdvarassign(const char *s) +{ + const char *p = skip_wdvarname(s, true); + + return (p != s && p[0] == CHAR && p[1] == '='); +} + +/* + * Make the exported environment from the exported names in the dictionary. + */ +char ** +makenv(void) +{ + struct block *l; + XPtrV denv; + struct tbl *vp, **vpp; + int i; + + XPinit(denv, 64); + for (l = e->loc; l != NULL; l = l->next) + for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; ) + if ((vp = *vpp++) != NULL && + (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) { + struct block *l2; + struct tbl *vp2; + uint32_t h = hash(vp->name); + + /* unexport any redefined instances */ + for (l2 = l->next; l2 != NULL; l2 = l2->next) { + vp2 = ktsearch(&l2->vars, vp->name, h); + if (vp2 != NULL) + vp2->flag &= ~EXPORT; + } + if ((vp->flag&INTEGER)) { + /* integer to string */ + char *val; + val = str_val(vp); + vp->flag &= ~(INTEGER|RDONLY|SPECIAL); + /* setstr can't fail here */ + setstr(vp, val, KSH_RETURN_ERROR); + } + XPput(denv, vp->val.s); + } + XPput(denv, NULL); + return ((char **)XPclose(denv)); +} + +/* Bob Jenkins' one-at-a-time hash */ +static uint32_t +oaathash_update(register uint32_t h, register const uint8_t *cp, + register size_t n) +{ + while (n--) { + h += *cp++; + h += h << 10; + h ^= h >> 6; + } + + return (h); +} + +static uint32_t +oaathash_finalise(register uint32_t h) +{ + h += h << 3; + h ^= h >> 11; + h += h << 15; + + return (h); +} + +uint32_t +oaathash_full(register const uint8_t *bp) +{ + register uint32_t h = 0; + register uint8_t c; + + while ((c = *bp++)) { + h += c; + h += h << 10; + h ^= h >> 6; + } + + return (oaathash_finalise(h)); +} + +void +change_random(const void *vp, size_t n) +{ + register uint32_t h = 0x100; +#if defined(__OpenBSD__) + int mib[2]; + uint8_t k[3]; + size_t klen; +#endif + + kshstate_v.cr_dp = vp; + kshstate_v.cr_dsz = n; + gettimeofday(&kshstate_v.cr_tv, NULL); + h = oaathash_update(oaathash_update(h, (void *)&kshstate_v, + sizeof(kshstate_v)), vp, n); + kshstate_v.lcg_state_ = oaathash_finalise(h); + +#if defined(__OpenBSD__) + /* OpenBSD, MirBSD: proper kernel entropy comes at zero cost */ + + mib[0] = CTL_KERN; + mib[1] = KERN_ARND; + klen = sizeof(k); + sysctl(mib, 2, k, &klen, &kshstate_v.lcg_state_, + sizeof(kshstate_v.lcg_state_)); + /* we ignore failures and take in k anyway */ + h = oaathash_update(h, k, sizeof(k)); + kshstate_v.lcg_state_ = oaathash_finalise(h); +#elif defined(MKSH_A4PB) + /* forced by the user to use arc4random_pushb(3) • Cygwin? */ + { + uint32_t prv; + + prv = arc4random_pushb(&kshstate_v.lcg_state_, + sizeof(kshstate_v.lcg_state_)); + h = oaathash_update(h, &prv, sizeof(prv)); + } + kshstate_v.lcg_state_ = oaathash_finalise(h); +#endif +} + +/* + * handle special variables with side effects - PATH, SECONDS. + */ + +/* Test if name is a special parameter */ +static int +special(const char *name) +{ + struct tbl *tp; + + tp = ktsearch(&specials, name, hash(name)); + return (tp && (tp->flag & ISSET) ? tp->type : V_NONE); +} + +/* Make a variable non-special */ +static void +unspecial(const char *name) +{ + struct tbl *tp; + + tp = ktsearch(&specials, name, hash(name)); + if (tp) + ktdelete(tp); +} + +static time_t seconds; /* time SECONDS last set */ +static int user_lineno; /* what user set $LINENO to */ + +static void +getspec(struct tbl *vp) +{ + register mksh_ari_t i; + int st; + + switch ((st = special(vp->name))) { + case V_SECONDS: + /* + * On start up the value of SECONDS is used before + * it has been set - don't do anything in this case + * (see initcoms[] in main.c). + */ + if (vp->flag & ISSET) { + struct timeval tv; + + gettimeofday(&tv, NULL); + i = tv.tv_sec - seconds; + } else + return; + break; + case V_RANDOM: + /* + * this is the same Linear Congruential PRNG as Borland + * C/C++ allegedly uses in its built-in rand() function + */ + i = ((kshstate_v.lcg_state_ = + 22695477 * kshstate_v.lcg_state_ + 1) >> 16) & 0x7FFF; + break; + case V_HISTSIZE: + i = histsize; + break; + case V_OPTIND: + i = user_opt.uoptind; + break; + case V_LINENO: + i = current_lineno + user_lineno; + break; + case V_COLUMNS: + case V_LINES: + /* + * Do NOT export COLUMNS/LINES. Many applications + * check COLUMNS/LINES before checking ws.ws_col/row, + * so if the app is started with C/L in the environ + * and the window is then resized, the app won't + * see the change cause the environ doesn't change. + */ + change_winsz(); + i = st == V_COLUMNS ? x_cols : x_lins; + break; + default: + /* do nothing, do not touch vp at all */ + return; + } + vp->flag &= ~SPECIAL; + setint(vp, i); + vp->flag |= SPECIAL; +} + +static void +setspec(struct tbl *vp) +{ + mksh_ari_t i; + char *s; + int st; + + switch ((st = special(vp->name))) { + case V_PATH: + if (path) + afree(path, APERM); + s = str_val(vp); + strdupx(path, s, APERM); + flushcom(1); /* clear tracked aliases */ + return; + case V_IFS: + setctypes(s = str_val(vp), C_IFS); + ifs0 = *s; + return; + case V_TMPDIR: + if (tmpdir) { + afree(tmpdir, APERM); + tmpdir = NULL; + } + /* Use tmpdir iff it is an absolute path, is writable and + * searchable and is a directory... + */ + { + struct stat statb; + + s = str_val(vp); + if (s[0] == '/' && access(s, W_OK|X_OK) == 0 && + stat(s, &statb) == 0 && S_ISDIR(statb.st_mode)) + strdupx(tmpdir, s, APERM); + } + break; +#if HAVE_PERSISTENT_HISTORY + case V_HISTFILE: + sethistfile(str_val(vp)); + break; +#endif + case V_TMOUT: + /* AT&T ksh seems to do this (only listen if integer) */ + if (vp->flag & INTEGER) + ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0; + break; + + /* common sub-cases */ + case V_OPTIND: + case V_HISTSIZE: + case V_COLUMNS: + case V_LINES: + case V_RANDOM: + case V_SECONDS: + case V_LINENO: + vp->flag &= ~SPECIAL; + i = intval(vp); + vp->flag |= SPECIAL; + break; + default: + /* do nothing, do not touch vp at all */ + return; + } + + /* process the singular parts of the common cases */ + + switch (st) { + case V_OPTIND: + getopts_reset((int)i); + break; + case V_HISTSIZE: + sethistsize((int)i); + break; + case V_COLUMNS: + if (i >= MIN_COLS) + x_cols = i; + break; + case V_LINES: + if (i >= MIN_LINS) + x_lins = i; + break; + case V_RANDOM: + /* + * mksh R39d+ no longer has the traditional repeatability + * of $RANDOM sequences, but always retains state + */ + change_random(&i, sizeof(i)); + break; + case V_SECONDS: + { + struct timeval tv; + + gettimeofday(&tv, NULL); + seconds = tv.tv_sec - i; + } + break; + case V_LINENO: + /* The -1 is because line numbering starts at 1. */ + user_lineno = (unsigned int)i - current_lineno - 1; + break; + } +} + +static void +unsetspec(struct tbl *vp) +{ + switch (special(vp->name)) { + case V_PATH: + if (path) + afree(path, APERM); + strdupx(path, def_path, APERM); + flushcom(1); /* clear tracked aliases */ + break; + case V_IFS: + setctypes(" \t\n", C_IFS); + ifs0 = ' '; + break; + case V_TMPDIR: + /* should not become unspecial */ + if (tmpdir) { + afree(tmpdir, APERM); + tmpdir = NULL; + } + break; + case V_LINENO: + case V_RANDOM: + case V_SECONDS: + case V_TMOUT: /* AT&T ksh leaves previous value in place */ + unspecial(vp->name); + break; + + /* + * AT&T ksh man page says OPTIND, OPTARG and _ lose special + * meaning, but OPTARG does not (still set by getopts) and _ is + * also still set in various places. Don't know what AT&T does + * for HISTSIZE, HISTFILE. Unsetting these in AT&T ksh does not + * loose the 'specialness': IFS, COLUMNS, PATH, TMPDIR + */ + } +} + +/* + * Search for (and possibly create) a table entry starting with + * vp, indexed by val. + */ +static struct tbl * +arraysearch(struct tbl *vp, uint32_t val) +{ + struct tbl *prev, *curr, *news; + size_t len; + + vp->flag = (vp->flag | (ARRAY|DEFINED)) & ~ASSOC; + /* The table entry is always [0] */ + if (val == 0) + return (vp); + prev = vp; + curr = vp->u.array; + while (curr && curr->ua.index < val) { + prev = curr; + curr = curr->u.array; + } + if (curr && curr->ua.index == val) { + if (curr->flag&ISSET) + return (curr); + news = curr; + } else + news = NULL; + len = strlen(vp->name) + 1; + if (!news) { + news = alloc(offsetof(struct tbl, name[0]) + len, vp->areap); + memcpy(news->name, vp->name, len); + } + news->flag = (vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL)) | AINDEX; + news->type = vp->type; + news->areap = vp->areap; + news->u2.field = vp->u2.field; + news->ua.index = val; + + if (curr != news) { /* not reusing old array entry */ + prev->u.array = news; + news->u.array = curr; + } + return (news); +} + +/* Return the length of an array reference (eg, [1+2]) - cp is assumed + * to point to the open bracket. Returns 0 if there is no matching closing + * bracket. + */ +int +array_ref_len(const char *cp) +{ + const char *s = cp; + int c; + int depth = 0; + + while ((c = *s++) && (c != ']' || --depth)) + if (c == '[') + depth++; + if (!c) + return (0); + return (s - cp); +} + +/* + * Make a copy of the base of an array name + */ +char * +arrayname(const char *str) +{ + const char *p; + char *rv; + + if ((p = cstrchr(str, '[')) == 0) + /* Shouldn't happen, but why worry? */ + strdupx(rv, str, ATEMP); + else + strndupx(rv, str, p - str, ATEMP); + + return (rv); +} + +/* set (or overwrite, if reset) the array variable var to the values in vals */ +mksh_uari_t +set_array(const char *var, bool reset, const char **vals) +{ + struct tbl *vp, *vq; + mksh_uari_t i; + const char *ccp; +#ifndef MKSH_SMALL + char *cp; + mksh_uari_t j; +#endif + + /* to get local array, use "typeset foo; set -A foo" */ + vp = global(var); + + /* Note: AT&T ksh allows set -A but not set +A of a read-only var */ + if ((vp->flag&RDONLY)) + errorf("%s: is read only", var); + /* This code is quite non-optimal */ + if (reset) + /* trash existing values and attributes */ + unset(vp, 1); + /* todo: would be nice for assignment to completely succeed or + * completely fail. Only really effects integer arrays: + * evaluation of some of vals[] may fail... + */ + i = 0; +#ifndef MKSH_SMALL + j = 0; +#else +#define j i +#endif + while ((ccp = vals[i])) { +#ifndef MKSH_SMALL + if (*ccp == '[') { + int level = 0; + + while (*ccp) { + if (*ccp == ']' && --level == 0) + break; + if (*ccp == '[') + ++level; + ++ccp; + } + if (*ccp == ']' && level == 0 && ccp[1] == '=') { + strndupx(cp, vals[i] + 1, ccp - (vals[i] + 1), + ATEMP); + evaluate(substitute(cp, 0), (mksh_ari_t *)&j, + KSH_UNWIND_ERROR, true); + afree(cp, ATEMP); + ccp += 2; + } else + ccp = vals[i]; + } +#endif + + vq = arraysearch(vp, j); + /* would be nice to deal with errors here... (see above) */ + setstr(vq, ccp, KSH_RETURN_ERROR); + i++; +#ifndef MKSH_SMALL + j++; +#endif + } + + return (i); +} + +void +change_winsz(void) +{ + if (x_lins < 0) { + /* first time initialisation */ +#ifdef TIOCGWINSZ + if (tty_fd < 0) + /* non-FTALKING, try to get an fd anyway */ + tty_init(false, false); +#endif + x_cols = -1; + } + +#ifdef TIOCGWINSZ + /* check if window size has changed */ + if (tty_fd >= 0) { + struct winsize ws; + + if (ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) { + if (ws.ws_col) + x_cols = ws.ws_col; + if (ws.ws_row) + x_lins = ws.ws_row; + } + } +#endif + + /* bounds check for sane values, use defaults otherwise */ + if (x_cols < MIN_COLS) + x_cols = 80; + if (x_lins < MIN_LINS) + x_lins = 24; + +#ifdef SIGWINCH + got_winch = 0; +#endif +} + +uint32_t +evilhash(const char *s) +{ + register uint32_t h = 0x100; + + h = oaathash_update(h, (void *)&kshstate_f, sizeof(kshstate_f)); + kshstate_f.h = oaathash_full((const uint8_t *)s); + return (oaathash_finalise(oaathash_update(h, + (void *)&kshstate_f.h, sizeof(kshstate_f.h)))); +} diff --git a/mksh/src/var_spec.h b/mksh/src/var_spec.h new file mode 100644 index 0000000..4035cc9 --- /dev/null +++ b/mksh/src/var_spec.h @@ -0,0 +1,39 @@ +#if defined(VARSPEC_DEFNS) +__RCSID("$MirOS: src/bin/mksh/var_spec.h,v 1.1 2009/09/26 03:40:03 tg Exp $"); +#define FN(name) /* nothing */ +#elif defined(VARSPEC_ENUMS) +#define FN(name) V_##name, +#define F0(name) V_##name = 0, +#elif defined(VARSPEC_ITEMS) +#define F0(name) /* nothing */ +#define FN(name) #name, +#endif + +#ifndef F0 +#define F0 FN +#endif + +/* 0 is always V_NONE */ +F0(NONE) + +/* 1 and up are special variables */ +FN(COLUMNS) +#if HAVE_PERSISTENT_HISTORY +FN(HISTFILE) +#endif +FN(HISTSIZE) +FN(IFS) +FN(LINENO) +FN(LINES) +FN(OPTIND) +FN(PATH) +FN(RANDOM) +FN(SECONDS) +FN(TMOUT) +FN(TMPDIR) + +#undef FN +#undef F0 +#undef VARSPEC_DEFNS +#undef VARSPEC_ENUMS +#undef VARSPEC_ITEMS diff --git a/netcfg/netcfg.c b/netcfg/netcfg.c index fc9cf48..9cd883a 100644 --- a/netcfg/netcfg.c +++ b/netcfg/netcfg.c @@ -19,17 +19,13 @@ #include <stdlib.h> #include <errno.h> #include <dirent.h> +#include <netinet/ether.h> + +#include <netutils/ifc.h> +#include <netutils/dhcp.h> static int verbose = 0; -int ifc_init(); -void ifc_close(); -int ifc_up(char *iname); -int ifc_down(char *iname); -int ifc_remove_host_routes(char *iname); -int ifc_remove_default_route(char *iname); -int ifc_get_info(const char *name, unsigned *addr, unsigned *mask, unsigned *flags); -int do_dhcp(char *iname); void die(const char *reason) { @@ -37,16 +33,12 @@ void die(const char *reason) exit(1); } -const char *ipaddr(unsigned addr) +const char *ipaddr(in_addr_t addr) { - static char buf[32]; - - sprintf(buf,"%d.%d.%d.%d", - addr & 255, - ((addr >> 8) & 255), - ((addr >> 16) & 255), - (addr >> 24)); - return buf; + struct in_addr in_addr; + + in_addr.s_addr = addr; + return inet_ntoa(in_addr); } void usage(void) @@ -86,6 +78,15 @@ int dump_interfaces(void) return 0; } +int set_hwaddr(const char *name, const char *asc) { + struct ether_addr *addr = ether_aton(asc); + if (!addr) { + printf("Failed to parse '%s'\n", asc); + return -1; + } + return ifc_set_hwaddr(name, addr->ether_addr_octet); +} + struct { const char *name; @@ -97,6 +98,7 @@ struct { "down", 1, ifc_down }, { "flhosts", 1, ifc_remove_host_routes }, { "deldefault", 1, ifc_remove_default_route }, + { "hwaddr", 2, set_hwaddr }, { 0, 0, 0 }, }; diff --git a/nexus/DhcpClient.cpp b/nexus/DhcpClient.cpp index a5654d2..713059d 100644 --- a/nexus/DhcpClient.cpp +++ b/nexus/DhcpClient.cpp @@ -27,35 +27,15 @@ #include <sysutils/ServiceManager.h> +#include <netutils/ifc.h> +#include <netutils/dhcp.h> + #include "DhcpClient.h" #include "DhcpState.h" #include "DhcpListener.h" #include "IDhcpEventHandlers.h" #include "Controller.h" -extern "C" { -int ifc_disable(const char *ifname); -int ifc_add_host_route(const char *ifname, uint32_t addr); -int ifc_remove_host_routes(const char *ifname); -int ifc_set_default_route(const char *ifname, uint32_t gateway); -int ifc_get_default_route(const char *ifname); -int ifc_remove_default_route(const char *ifname); -int ifc_reset_connections(const char *ifname); -int ifc_configure(const char *ifname, in_addr_t ipaddr, in_addr_t netmask, in_addr_t gateway, in_addr_t dns1, in_addr_t dns2); - -int dhcp_do_request(const char *ifname, - in_addr_t *ipaddr, - in_addr_t *gateway, - in_addr_t *mask, - in_addr_t *dns1, - in_addr_t *dns2, - in_addr_t *server, - uint32_t *lease); -int dhcp_stop(const char *ifname); -int dhcp_release_lease(const char *ifname); -char *dhcp_get_errmsg(); -} - DhcpClient::DhcpClient(IDhcpEventHandlers *handlers) : mState(DhcpState::INIT), mHandlers(handlers) { mServiceManager = new ServiceManager(); diff --git a/patch.txt b/patch.txt new file mode 100644 index 0000000..258965d --- /dev/null +++ b/patch.txt @@ -0,0 +1,16 @@ +diff --git a/init/util.c b/init/util.c +index 4d98cc2..0667593 100755 +--- a/init/util.c ++++ b/init/util.c +@@ -657,8 +657,9 @@ static void get_hardware_name(void) + if (x) { + x += 2; + n = 0; +- while (*x && !isspace(*x)) { +- hardware[n++] = tolower(*x); ++ while (*x && *x != '\n') { ++ if (!isspace(*x)) ++ hardware[n++] = tolower(*x); + x++; + if (n == 31) break; + } diff --git a/rootdir/Android.mk b/rootdir/Android.mk index 329be7f..380bb60 100644 --- a/rootdir/Android.mk +++ b/rootdir/Android.mk @@ -7,7 +7,7 @@ copy_from := \ etc/dbus.conf \ etc/hosts -ifeq ($(TARGET_PRODUCT),generic) +ifeq ($(TARGET_PRODUCT),full) copy_from += etc/vold.fstab endif diff --git a/rootdir/etc/init.goldfish.sh b/rootdir/etc/init.goldfish.sh index 5ff0a3a..cfa2c82 100755 --- a/rootdir/etc/init.goldfish.sh +++ b/rootdir/etc/init.goldfish.sh @@ -45,3 +45,13 @@ esac # this line doesn't really do anything useful. however without it the # previous setprop doesn't seem to apply for some really odd reason setprop ro.qemu.init.completed 1 + +# set up the second interface (for inter-emulator connections) +# if required +my_ip=`getprop net.shared_net_ip` +case "$my_ip" in + "") + ;; + *) ifconfig eth1 "$my_ip" netmask 255.255.255.0 up + ;; +esac diff --git a/rootdir/init.rc b/rootdir/init.rc index 208ad42..6221e6e 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -176,6 +176,11 @@ on post-fs chown root root /cache/lost+found chmod 0770 /cache/lost+found + # create data/drm directory + mkdir /data/drm 0774 drm drm + chown drm drm /data/drm + chmod 0774 /data/drm + on boot # basic network init ifup lo @@ -324,6 +329,13 @@ service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-sys onrestart write /sys/power/state on onrestart restart media +service drm /system/bin/drmserver + user drm + group system root inet + +service drmio /system/bin/drmioserver + user drmio + service media /system/bin/mediaserver user media group system audio camera graphics inet net_bt net_bt_admin net_raw diff --git a/sh/Android.mk b/sh/Android.mk index b5e5c38..dcd13d8 100644 --- a/sh/Android.mk +++ b/sh/Android.mk @@ -29,7 +29,8 @@ LOCAL_SRC_FILES:= \ bltin/echo.c \ init.c -LOCAL_MODULE:= sh +LOCAL_MODULE:= ash +LOCAL_MODULE_TAGS:= shell_ash LOCAL_CFLAGS += -DSHELL -DWITH_LINENOISE @@ -51,3 +52,19 @@ make_ash_files: sh ./mkinit.sh $(PRIVATE_SRC_FILES) include $(BUILD_EXECUTABLE) + + +# create /system/bin/sh symlink to $(TARGET_SHELL) +# not the optimal place for this, but a fitting one + +OUTSYSTEMBINSH := $(TARGET_OUT)/bin/sh +LOCAL_MODULE := systembinsh +$(OUTSYSTEMBINSH): | $(TARGET_SHELL) +$(OUTSYSTEMBINSH): LOCAL_MODULE := $(LOCAL_MODULE) +$(OUTSYSTEMBINSH): + @echo "Symlink: $@ -> $(TARGET_SHELL)" + @rm -rf $@ + $(hide) ln -sf $(TARGET_SHELL) $@ + +ALL_DEFAULT_INSTALLED_MODULES += $(OUTSYSTEMBINSH) +ALL_MODULES.$(LOCAL_MODULE).INSTALLED += $(OUTSYSTEMBINSH) diff --git a/toolbox/Android.mk b/toolbox/Android.mk index 588dac0..ef3980a 100644 --- a/toolbox/Android.mk +++ b/toolbox/Android.mk @@ -41,7 +41,7 @@ TOOLS := \ printenv \ smd \ chmod \ - chown \ + chown \ newfs_msdos \ netstat \ ioctl \ diff --git a/toolbox/chmod.c b/toolbox/chmod.c index 31a53bf..2a524e9 100644 --- a/toolbox/chmod.c +++ b/toolbox/chmod.c @@ -4,17 +4,74 @@ #include <sys/types.h> #include <dirent.h> #include <errno.h> +#include <sys/limits.h> +#include <sys/stat.h> #include <unistd.h> #include <time.h> +void recurse_chmod(char* path, int mode) +{ + struct dirent *dp; + DIR *dir = opendir(path); + if (dir == NULL) { + // not a directory, carry on + return; + } + char *subpath = malloc(sizeof(char)*PATH_MAX); + int pathlen = strlen(path); + + while ((dp = readdir(dir)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || + strcmp(dp->d_name, "..") == 0) continue; + + if (strlen(dp->d_name) + pathlen + 2/*NUL and slash*/ > PATH_MAX) { + fprintf(stderr, "Invalid path specified: too long\n"); + exit(1); + } + + strcpy(subpath, path); + strcat(subpath, "/"); + strcat(subpath, dp->d_name); + + if (chmod(subpath, mode) < 0) { + fprintf(stderr, "Unable to chmod %s: %s\n", subpath, strerror(errno)); + exit(1); + } + + recurse_chmod(subpath, mode); + } + free(subpath); + closedir(dir); +} + +static int usage() +{ + fprintf(stderr, "Usage: chmod [OPTION] <MODE> <FILE>\n"); + fprintf(stderr, " -R, --recursive change files and directories recursively\n"); + fprintf(stderr, " --help display this help and exit\n"); + + return 10; +} + int chmod_main(int argc, char **argv) { int i; - if (argc < 3) { - fprintf(stderr, "Usage: chmod <MODE> <FILE>\n"); - return 10; + if (argc < 3 || strcmp(argv[1], "--help") == 0) { + return usage(); + } + + int recursive = (strcmp(argv[1], "-R") == 0 || + strcmp(argv[1], "--recursive") == 0) ? 1 : 0; + + if (recursive && argc < 4) { + return usage(); + } + + if (recursive) { + argc--; + argv++; } int mode = 0; @@ -29,11 +86,15 @@ int chmod_main(int argc, char **argv) } s++; } + for (i = 2; i < argc; i++) { if (chmod(argv[i], mode) < 0) { fprintf(stderr, "Unable to chmod %s: %s\n", argv[i], strerror(errno)); return 10; } + if (recursive) { + recurse_chmod(argv[i], mode); + } } return 0; } diff --git a/toolbox/insmod.c b/toolbox/insmod.c index 44b9847..756a64b 100644 --- a/toolbox/insmod.c +++ b/toolbox/insmod.c @@ -77,7 +77,6 @@ int insmod_main(int argc, char **argv) memcpy(ptr, argv[i], len); ptr += len; *ptr++ = ' '; - *ptr++ = '\0'; } *(ptr - 1) = '\0'; } diff --git a/toolbox/ls.c b/toolbox/ls.c index 8799514..962bf47 100644 --- a/toolbox/ls.c +++ b/toolbox/ls.c @@ -13,6 +13,130 @@ #include <grp.h> #include <linux/kdev_t.h> +#include <limits.h> + +// dynamic arrays +typedef struct { + int count; + int capacity; + void** items; +} dynarray_t; + +#define DYNARRAY_INITIALIZER { 0, 0, NULL } + +static void dynarray_init( dynarray_t *a ) +{ + a->count = a->capacity = 0; + a->items = NULL; +} + +static void dynarray_reserve_more( dynarray_t *a, int count ) +{ + int old_cap = a->capacity; + int new_cap = old_cap; + const int max_cap = INT_MAX/sizeof(void*); + void** new_items; + int new_count = a->count + count; + + if (count <= 0) + return; + + if (count > max_cap - a->count) + abort(); + + new_count = a->count + count; + + while (new_cap < new_count) { + old_cap = new_cap; + new_cap += (new_cap >> 2) + 4; + if (new_cap < old_cap || new_cap > max_cap) { + new_cap = max_cap; + } + } + new_items = realloc(a->items, new_cap*sizeof(void*)); + if (new_items == NULL) + abort(); + + a->items = new_items; + a->capacity = new_cap; +} + +static void dynarray_append( dynarray_t *a, void* item ) +{ + if (a->count >= a->capacity) + dynarray_reserve_more(a, 1); + + a->items[a->count++] = item; +} + +static void dynarray_done( dynarray_t *a ) +{ + free(a->items); + a->items = NULL; + a->count = a->capacity = 0; +} + +#define DYNARRAY_FOREACH_TYPE(_array,_item_type,_item,_stmnt) \ + do { \ + int _nn_##__LINE__ = 0; \ + for (;_nn_##__LINE__ < (_array)->count; ++ _nn_##__LINE__) { \ + _item_type _item = (_item_type)(_array)->items[_nn_##__LINE__]; \ + _stmnt; \ + } \ + } while (0) + +#define DYNARRAY_FOREACH(_array,_item,_stmnt) \ + DYNARRAY_FOREACH_TYPE(_array,void *,_item,_stmnt) + +// string arrays + +typedef dynarray_t strlist_t; + +#define STRLIST_INITIALIZER DYNARRAY_INITIALIZER + +#define STRLIST_FOREACH(_list,_string,_stmnt) \ + DYNARRAY_FOREACH_TYPE(_list,char *,_string,_stmnt) + +static void strlist_init( strlist_t *list ) +{ + dynarray_init(list); +} + +static void strlist_append_b( strlist_t *list, const void* str, size_t slen ) +{ + char *copy = malloc(slen+1); + memcpy(copy, str, slen); + copy[slen] = '\0'; + dynarray_append(list, copy); +} + +static void strlist_append_dup( strlist_t *list, const char *str) +{ + strlist_append_b(list, str, strlen(str)); +} + +static void strlist_done( strlist_t *list ) +{ + STRLIST_FOREACH(list, string, free(string)); + dynarray_done(list); +} + +static int strlist_compare_strings(const void* a, const void* b) +{ + const char *sa = *(const char **)a; + const char *sb = *(const char **)b; + return strcmp(sa, sb); +} + +static void strlist_sort( strlist_t *list ) +{ + if (list->count > 0) { + qsort(list->items, + (size_t)list->count, + sizeof(void*), + strlist_compare_strings); + } +} // bits for flags argument #define LIST_LONG (1 << 0) @@ -233,7 +357,8 @@ static int listdir(const char *name, int flags) char tmp[4096]; DIR *d; struct dirent *de; - + strlist_t files = STRLIST_INITIALIZER; + d = opendir(name); if(d == 0) { fprintf(stderr, "opendir failed, %s\n", strerror(errno)); @@ -248,10 +373,16 @@ static int listdir(const char *name, int flags) if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) continue; if(de->d_name[0] == '.' && (flags & LIST_ALL) == 0) continue; - listfile(name, de->d_name, flags); + strlist_append_dup(&files, de->d_name); } + strlist_sort(&files); + STRLIST_FOREACH(&files, filename, listfile(name, filename, flags)); + strlist_done(&files); + if (flags & LIST_RECURSIVE) { + strlist_t subdirs = STRLIST_INITIALIZER; + rewinddir(d); while ((de = readdir(d)) != 0) { @@ -284,10 +415,15 @@ static int listdir(const char *name, int flags) } if (S_ISDIR(s.st_mode)) { - printf("\n%s:\n", tmp); - listdir(tmp, flags); + strlist_append_dup(&subdirs, tmp); } } + strlist_sort(&subdirs); + STRLIST_FOREACH(&subdirs, path, { + printf("\n%s:\n", path); + listdir(path, flags); + }); + strlist_done(&subdirs); } closedir(d); @@ -331,27 +467,40 @@ int ls_main(int argc, char **argv) if(argc > 1) { int i; int err = 0; + strlist_t files = STRLIST_INITIALIZER; for (i = 1; i < argc; i++) { - if (!strcmp(argv[i], "-l")) { - flags |= LIST_LONG; - } else if (!strcmp(argv[i], "-s")) { - flags |= LIST_SIZE; - } else if (!strcmp(argv[i], "-a")) { - flags |= LIST_ALL; - } else if (!strcmp(argv[i], "-R")) { - flags |= LIST_RECURSIVE; - } else if (!strcmp(argv[i], "-d")) { - flags |= LIST_DIRECTORIES; - } else { - listed++; - if(listpath(argv[i], flags) != 0) { - err = EXIT_FAILURE; + if (argv[i][0] == '-') { + /* an option ? */ + const char *arg = argv[i]+1; + while (arg[0]) { + switch (arg[0]) { + case 'l': flags |= LIST_LONG; break; + case 's': flags |= LIST_SIZE; break; + case 'R': flags |= LIST_RECURSIVE; break; + case 'd': flags |= LIST_DIRECTORIES; break; + case 'a': flags |= LIST_ALL; break; + default: + fprintf(stderr, "%s: Unknown option '-%c'. Aborting.\n", "ls", arg[0]); + exit(1); + } + arg++; } + } else { + /* not an option ? */ + strlist_append_dup(&files, argv[i]); } } - if (listed > 0) return err; + if (files.count > 0) { + STRLIST_FOREACH(&files, path, { + if (listpath(path, flags) != 0) { + err = EXIT_FAILURE; + } + }); + strlist_done(&files); + return err; + } } // list working directory if no files or directories were specified diff --git a/toolbox/mkdir.c b/toolbox/mkdir.c index 121adab..656970a 100644 --- a/toolbox/mkdir.c +++ b/toolbox/mkdir.c @@ -2,10 +2,14 @@ #include <unistd.h> #include <string.h> #include <errno.h> +#include <sys/limits.h> +#include <sys/stat.h> static int usage() { - fprintf(stderr,"mkdir <target>\n"); + fprintf(stderr,"mkdir [OPTION] <target>\n"); + fprintf(stderr," --help display usage and exit\n"); + fprintf(stderr," -p, --parents create parent directories as needed\n"); return -1; } @@ -13,15 +17,60 @@ int mkdir_main(int argc, char *argv[]) { int symbolic = 0; int ret; - if(argc < 2) return usage(); + if(argc < 2 || strcmp(argv[1], "--help") == 0) { + return usage(); + } + + int recursive = (strcmp(argv[1], "-p") == 0 || + strcmp(argv[1], "--parents") == 0) ? 1 : 0; + + if(recursive && argc < 3) { + // -p specified without a path + return usage(); + } + + if(recursive) { + argc--; + argv++; + } + + char currpath[PATH_MAX], *pathpiece; + struct stat st; while(argc > 1) { argc--; argv++; - ret = mkdir(argv[0], 0777); - if(ret < 0) { - fprintf(stderr, "mkdir failed for %s, %s\n", argv[0], strerror(errno)); - return ret; + if(recursive) { + // reset path + strcpy(currpath, ""); + // create the pieces of the path along the way + pathpiece = strtok(argv[0], "/"); + if(argv[0][0] == '/') { + // prepend / if needed + strcat(currpath, "/"); + } + while(pathpiece != NULL) { + if(strlen(currpath) + strlen(pathpiece) + 2/*NUL and slash*/ > PATH_MAX) { + fprintf(stderr, "Invalid path specified: too long\n"); + return 1; + } + strcat(currpath, pathpiece); + strcat(currpath, "/"); + if(stat(currpath, &st) != 0) { + ret = mkdir(currpath, 0777); + if(ret < 0) { + fprintf(stderr, "mkdir failed for %s, %s\n", currpath, strerror(errno)); + return ret; + } + } + pathpiece = strtok(NULL, "/"); + } + } else { + ret = mkdir(argv[0], 0777); + if(ret < 0) { + fprintf(stderr, "mkdir failed for %s, %s\n", argv[0], strerror(errno)); + return ret; + } } } diff --git a/toolbox/route.c b/toolbox/route.c index 107e48a..3e10014 100644 --- a/toolbox/route.c +++ b/toolbox/route.c @@ -80,14 +80,24 @@ int route_main(int argc, char *argv[]) /* route add -net 192.168.1.2 netmask 255.255.255.0 gw 192.168.1.1 */ if (argc > 7 && !strcmp(argv[2], "-net") && - !strcmp(argv[4], "netmask") && !strcmp(argv[6], "gw")) { - rt.rt_flags = RTF_UP | RTF_GATEWAY; - if (set_address(argv[3], &rt.rt_dst) && - set_address(argv[5], &rt.rt_genmask) && - set_address(argv[7], &rt.rt_gateway)) { - errno = 0; + !strcmp(argv[4], "netmask")) { + if (!strcmp(argv[6], "gw")) { + rt.rt_flags = RTF_UP | RTF_GATEWAY; + if (set_address(argv[3], &rt.rt_dst) && + set_address(argv[5], &rt.rt_genmask) && + set_address(argv[7], &rt.rt_gateway)) { + errno = 0; + } + goto apply; + } else if (!strcmp(argv[6], "dev")) { + rt.rt_flags = RTF_UP; + rt.rt_dev = argv[7]; + if (set_address(argv[3], &rt.rt_dst) && + set_address(argv[5], &rt.rt_genmask)) { + errno = 0; + } + goto apply; } - goto apply; } } |