/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "utility.h" #include "initialize.h" #include "statistics.h" #include "streams_common.h" #include "streams.h" /* * utility * * Overview * utility.c contains the general purpose routines used in various locations * throughout sort. It provides a number of interfaces that maintain local * state relevant to this instance of sort. We discuss the more significant * of these interfaces below. * * Output guard * sort is one of the few Unix utilities that is capable of working "in * place"; that is, sort can manipulate an input file and place its output in * a file of the same name safely. This is handled in this implementation by * the output guard facility. In the case of an interrupt or other fatal * signal, sort essays to restore the original input file. * * Temporary file cleanup * Similar to the output guard facility, sort cleans up its temporary files in * the case of interruption (or normal exit, for that matter); this is handled * by registering a list of file pointers for later use by the atexit handler. * * Temporary filename security * sort protects against "open-through-link" security attacks by verifying * that the selected temporary file name is unused. If the file name is in * use, the pattern is readjusted until an available name pattern is * discovered. * * Buffered I/O * sort has a simple buffered I/O facility of its own, to facilitate writing * data in large quantities (particularly for multibyte locales). cxwrite() * is the base routine, while wxwrite(), which handles multibyte buffers, is * built on top of cxwrite(). */ #define XBUFFER_SIZE (32 * KILOBYTE) #define EXIT_OK 0 #define EXIT_FAILURE 1 #define EXIT_ERROR 2 #define EXIT_INTERNAL 3 static int held_fd = -1; static stream_t **cleanup_chain = NULL; static char *output_guard_tempname = NULL; static ssize_t output_guard_size = 0; static char *output_guard_filename = NULL; static int output_guard_copy_complete = 0; static const char *default_tmpdir = "/var/tmp"; static const char *default_template = "/stmAAAXXXXXX"; static const char *default_template_count = ".00000000"; static char *current_tmpdir; static char *current_template; static const char PNAME_FMT[] = "%s: "; static const char ERRNO_FMT[] = ": %s\n"; static const char *pname = "sort"; void swap(void **a, void **b) { void *t; t = *a; *a = *b; *b = t; __S(stats_incr_swaps()); } /* * Temporary file name template handling. */ static void reset_file_template() { struct stat s; do { (void) strcpy(current_template, current_tmpdir); (void) strcat(current_template, default_template); (void) mktemp(current_template); (void) strcat(current_template, default_template_count); } while (lstat(current_template, &s) != -1); } int bump_file_template() { struct stat s; int n = strlen(current_template); int i; for (i = n - 1; isdigit((uchar_t)current_template[i]); i--) { current_template[i]++; if (current_template[i] > '9') current_template[i] = '0'; else break; } if (!isdigit((uchar_t)current_template[i])) { /* * Template has been exhausted, so reset. */ reset_file_template(); } if (lstat(current_template, &s) == 0) { /* * Our newly bumped template has been anticipated; reset to * avoid possible "link-through" attack. */ reset_file_template(); } return (0); } void set_file_template(char **T) { struct stat s; int check_tmpdir = 0; if (*T != NULL) { current_tmpdir = strdup(*T); check_tmpdir = 1; } else if ((current_tmpdir = getenv("TMPDIR")) != NULL) { check_tmpdir = 1; } else { current_tmpdir = (char *)default_tmpdir; } /* * Check that the temporary directory given exists, and is a directory. */ if (check_tmpdir) { if (stat(current_tmpdir, &s) != 0) { warn(gettext("cannot stat temporary directory %s"), current_tmpdir); current_tmpdir = (char *)default_tmpdir; } else if (!S_ISDIR(s.st_mode)) { warn(gettext("%s is not a directory; " "using default temporary directory"), current_tmpdir); current_tmpdir = (char *)default_tmpdir; } } ASSERT(current_tmpdir != NULL); current_template = safe_realloc(NULL, strlen(current_tmpdir) + strlen(default_template) + strlen(default_template_count) + 1); reset_file_template(); } char * get_file_template() { return (current_template); } /* * Output guard routines. */ void establish_output_guard(sort_t *S) { struct stat output_stat; if (S->m_output_to_stdout) return; if (stat(S->m_output_filename, &output_stat) == 0) { stream_t *strp = S->m_input_streams; while (strp != NULL) { /* * We needn't protect an empty file. */ if (!(strp->s_status & STREAM_NOTFILE) && strp->s_dev == output_stat.st_dev && strp->s_ino == output_stat.st_ino && strp->s_filesize > 0) { output_guard_filename = S->m_output_filename; output_guard_size = strp->s_filesize; ASSERT(output_guard_filename != NULL); if (bump_file_template() < 0) die(EMSG_TEMPORARY); if ((strp->s_filename = output_guard_tempname = strdup(get_file_template())) == NULL) die(EMSG_ALLOC); xcp(output_guard_tempname, output_guard_filename, output_guard_size); output_guard_copy_complete = 1; return; } strp = strp->s_next; } } } void remove_output_guard() { if (output_guard_tempname && unlink(output_guard_tempname) == -1) warn(gettext("unable to unlink %s"), output_guard_tempname); output_guard_tempname = NULL; } void set_cleanup_chain(stream_t **strp) { ASSERT(strp != NULL); cleanup_chain = strp; } /* * atexit_handler() cleans up any temporary files outstanding after a fatal * signal, a call to die() or at exit(). To preserve the input file under low * storage conditions (and both the output file and the temporary files are * directed at the same filesystem), we remove all temporary files but the * output guard first, and then restore the original file. Of course, this is * not foolproof, as another writer may have exhausted storage. */ void atexit_handler() { stream_t *strp; if (cleanup_chain && *cleanup_chain) for (strp = *cleanup_chain; strp != NULL; strp = strp->s_next) stream_unlink_temporary(strp); if (output_guard_tempname) { if (output_guard_copy_complete) xcp(output_guard_filename, output_guard_tempname, output_guard_size); remove_output_guard(); } __S(stats_display()); } size_t strtomem(char *S) { const char *format_str = "%lf%c"; double val = 0.0; size_t retval; char units = 'k'; size_t phys_total = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE); if (sscanf(S, format_str, &val, &units) < 1 || val < 0) return (0); if (units == '%') { if (val < 0 || val > 100) return (0); val *= phys_total / 100; } else switch (units) { case 't' : /* terabytes */ case 'T' : val *= 1024; /*FALLTHROUGH*/ case 'g' : /* gigabytes */ case 'G' : val *= 1024; /*FALLTHROUGH*/ case 'm' : /* megabytes */ case 'M' : val *= 1024; /*FALLTHROUGH*/ case 'k' : /* kilobytes */ case 'K' : val *= 1024; /*FALLTHROUGH*/ case 'b' : /* bytes */ case 'B' : break; default : /* * default is kilobytes */ val *= 1024; break; } if (val > SIZE_MAX) return (0); retval = (size_t)val; return (retval); } size_t available_memory(size_t mem_limit) { size_t phys_avail = sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); size_t avail; if (mem_limit != 0) { #ifdef DEBUG /* * In the debug case, we want to test the temporary files * handling, so no lower bound on the memory limit is imposed. */ avail = mem_limit; #else avail = MAX(64 * KILOBYTE, mem_limit); #endif /* DEBUG */ } else { avail = MAX(64 * KILOBYTE, MIN(AV_MEM_MULTIPLIER * phys_avail / AV_MEM_DIVISOR, 16 * MEGABYTE)); } __S(stats_set_available_memory(avail)); return (avail); } void set_memory_ratio(sort_t *S, int *numerator, int *denominator) { if (S->m_c_locale) { *numerator = CHAR_AVG_LINE; *denominator = sizeof (line_rec_t) + sizeof (line_rec_t *) + CHAR_AVG_LINE + CHAR_AVG_LINE; return; } if (S->m_single_byte_locale) { *numerator = CHAR_AVG_LINE; *denominator = sizeof (line_rec_t) + sizeof (line_rec_t *) + CHAR_AVG_LINE + XFRM_MULTIPLIER * CHAR_AVG_LINE; return; } *numerator = WCHAR_AVG_LINE; *denominator = sizeof (line_rec_t) + sizeof (line_rec_t *) + WCHAR_AVG_LINE + WCHAR_AVG_LINE; } void * safe_realloc(void *ptr, size_t sz) { /* * safe_realloc() is not meant as an alternative free() mechanism--we * disallow reallocations to size zero. */ ASSERT(sz != 0); if ((ptr = realloc(ptr, sz)) != NULL) return (ptr); die(gettext("unable to reallocate buffer")); /*NOTREACHED*/ return (NULL); /* keep gcc happy */ } void safe_free(void *ptr) { if (ptr) free(ptr); } void * xzmap(void *addr, size_t len, int prot, int flags, off_t off) { void *pa; pa = mmap(addr, len, prot, flags | MAP_ANON, -1, off); if (pa == MAP_FAILED) die(gettext("can't mmap anonymous memory")); return (pa); } void usage() { (void) fprintf(stderr, gettext("usage: %s [-cmu] [-o output] [-T directory] [-S mem]" " [-z recsz]\n\t[-dfiMnr] [-b] [-t char] [-k keydef]" " [+pos1 [-pos2]] files...\n"), CMDNAME); exit(E_USAGE); } /* * hold_file_descriptor() and release_file_descriptor() reserve a single file * descriptor entry for later use. We issue the hold prior to any loop that has * an exit condition based on the receipt of EMFILE from an open() call; once we * have exited, we can release, typically prior to opening a file for output. */ void hold_file_descriptor() { ASSERT(held_fd == -1); if ((held_fd = open("/dev/null", O_RDONLY)) == -1) die(gettext("insufficient available file descriptors\n")); } void release_file_descriptor() { ASSERT(held_fd != -1); (void) close(held_fd); held_fd = -1; } void copy_line_rec(const line_rec_t *a, line_rec_t *b) { (void) memcpy(b, a, sizeof (line_rec_t)); } void trip_eof(FILE *f) { if (feof(f)) return; (void) ungetc(fgetc(f), f); } /* * int cxwrite(int, char *, size_t) * * Overview * cxwrite() implements a buffered version of fwrite(ptr, nbytes, 1, .) on * file descriptors. It returns -1 in the case that the write() fails to * write the current buffer contents. cxwrite() must be flushed before being * applied to a new file descriptor. * * Return values * 0 on success, -1 on error. */ int cxwrite(int fd, char *ptr, size_t nbytes) { static char buffer[XBUFFER_SIZE]; static size_t offset = 0; size_t mbytes; if (ptr == NULL) { errno = 0; while (offset -= write(fd, buffer, offset)) { if (errno) break; } if (offset) return (-1); return (0); } while (nbytes != 0) { if (offset + nbytes > XBUFFER_SIZE) mbytes = XBUFFER_SIZE - offset; else mbytes = nbytes; (void) memcpy(buffer + offset, ptr, mbytes); nbytes -= mbytes; offset += mbytes; ptr += mbytes; if (nbytes) { errno = 0; while (offset -= write(fd, buffer, offset)) { if (errno) break; } if (offset) return (-1); } } return (0); } /* * int wxwrite(int, wchar_t *) * * Overview * wxwrite() implements a buffered write() function for null-terminated wide * character buffers with similar calling semantics to cxwrite(). It returns * -1 in the case that it fails to write the current buffer contents. * wxwrite() must be flushed before being applied to a new file descriptor. * * Return values * 0 on success, -1 on error. */ int wxwrite(int fd, wchar_t *ptr) { static char *convert_buffer; static size_t convert_bufsize = 1024; size_t req_bufsize; if (ptr == NULL) return (cxwrite(fd, NULL, 0)); if (convert_buffer == NULL) convert_buffer = safe_realloc(NULL, convert_bufsize); /* * We use wcstombs(NULL, ., .) to verify that we have an adequate * buffer size for the conversion. Since this buffer was converted into * wide character format earlier, we can safely assume that the buffer * can be converted back to the external multibyte form. */ req_bufsize = wcstombs(NULL, ptr, convert_bufsize); if (req_bufsize > convert_bufsize) { convert_bufsize = req_bufsize + 1; convert_buffer = safe_realloc(convert_buffer, convert_bufsize); } (void) wcstombs(convert_buffer, ptr, convert_bufsize); return (cxwrite(fd, convert_buffer, req_bufsize)); } int xstreql(const char *a, const char *b) { return (strcmp(a, b) == 0); } int xstrneql(const char *a, const char *b, const size_t l) { return (strncmp(a, b, l) == 0); } char * xstrnchr(const char *S, const int c, const size_t n) { const char *eS = S + n; do { if (*S == (char)c) return ((char *)S); } while (++S < eS); return (NULL); } void xstrninv(char *s, ssize_t start, ssize_t length) { ssize_t i; for (i = start; i < start + length; i++) s[i] = UCHAR_MAX - s[i]; } int xwcsneql(const wchar_t *a, const wchar_t *b, const size_t length) { return (wcsncmp(a, b, length) == 0); } wchar_t * xwsnchr(const wchar_t *ws, const wint_t wc, const size_t n) { const wchar_t *ews = ws + n; do { if (*ws == (wchar_t)wc) return ((wchar_t *)ws); } while (++ws < ews); return (NULL); } void xwcsninv(wchar_t *s, ssize_t start, ssize_t length) { ssize_t i; for (i = start; i < start + length; i++) s[i] = WCHAR_MAX - s[i]; } #ifdef _LITTLE_ENDIAN void xwcsntomsb(wchar_t *s, ssize_t length) { ssize_t i; ASSERT(sizeof (wchar_t) == sizeof (uint32_t)); for (i = 0; i < length; i++, s++) { char *t = (char *)s; char u; u = *t; *t = *(t + 3); *(t + 3) = u; u = *(t + 1); *(t + 1) = *(t + 2); *(t + 2) = u; } } #endif /* _LITTLE_ENDIAN */ wchar_t * xmemwchar(wchar_t *s, wchar_t w, ssize_t length) { ssize_t i = length; while (--i > 0) { if (*s == w) return (s); s++; } return (NULL); } void xcp(char *dst, char *src, off_t size) { int fd_in, fd_out; void *mm_in; size_t chunksize = 2 * MEGABYTE; int i; ssize_t nchunks = size / chunksize; ssize_t lastchunk = size % chunksize; if (dst == NULL || src == NULL) return; if ((fd_in = open(src, O_RDONLY)) < 0) die(EMSG_OPEN, src); if ((fd_out = open(dst, O_RDWR | O_CREAT | O_TRUNC, OUTPUT_MODE)) < 0) die(EMSG_OPEN, dst); for (i = 0; i < nchunks; i++) { if ((mm_in = mmap(0, chunksize, PROT_READ, MAP_SHARED, fd_in, i * chunksize)) == MAP_FAILED) die(EMSG_MMAP, src); if (write(fd_out, mm_in, chunksize) != chunksize) die(EMSG_WRITE, dst); (void) munmap(mm_in, chunksize); } if (lastchunk) { if ((mm_in = mmap(0, lastchunk, PROT_READ, MAP_SHARED, fd_in, nchunks * chunksize)) == MAP_FAILED) die(EMSG_MMAP, src); if (write(fd_out, mm_in, lastchunk) != lastchunk) die(EMSG_WRITE, dst); (void) munmap(mm_in, lastchunk); } (void) close(fd_in); if (close(fd_out) == -1) die(EMSG_CLOSE, dst); } /*PRINTFLIKE1*/ void warn(const char *format, ...) { int err = errno; va_list alist; if (pname != NULL) (void) fprintf(stderr, gettext(PNAME_FMT), pname); va_start(alist, format); (void) vfprintf(stderr, format, alist); va_end(alist); if (strrchr(format, '\n') == NULL) (void) fprintf(stderr, gettext(ERRNO_FMT), strerror(err)); } /*PRINTFLIKE1*/ void die(const char *format, ...) { int err = errno; va_list alist; if (pname != NULL) (void) fprintf(stderr, gettext(PNAME_FMT), pname); va_start(alist, format); (void) vfprintf(stderr, format, alist); va_end(alist); if (strrchr(format, '\n') == NULL) (void) fprintf(stderr, gettext(ERRNO_FMT), strerror(err)); exit(E_ERROR); } #ifdef DEBUG /* * pprintc() is called only by xdump(). */ #define BYTES_PER_LINE 16 static void pprintc(FILE *fp, char c) { if (isspace((uchar_t)c)) (void) fprintf(fp, " "); else if (isprint((uchar_t)c)) (void) fprintf(fp, "%c", c); else (void) fprintf(fp, "."); } static void pprintwc(FILE *fp, wchar_t c) { if (iswspace(c)) (void) fprintf(fp, " "); else if (iswprint(c)) (void) fprintf(fp, "%wc", c); else (void) fprintf(fp, "."); } /* * xdump() is used only for debugging purposes. */ void xdump(FILE *fp, uchar_t *buf, size_t bufsize, int wide) { int i; size_t nc = 0; uchar_t d[BYTES_PER_LINE]; for (; nc < bufsize; buf++) { d[nc % BYTES_PER_LINE] = *buf; if (nc % BYTES_PER_LINE == 0) { (void) fprintf(fp, "%08x:", nc); } (void) fprintf(fp, " %02x", *buf); nc++; if (nc % BYTES_PER_LINE == 0) { (void) fprintf(fp, " "); if (wide) { for (i = 0; i < BYTES_PER_LINE; i += sizeof (wchar_t)) pprintwc(fp, *(wchar_t *)(d + i)); } else { for (i = 0; i < BYTES_PER_LINE; i++) pprintc(fp, d[i]); } (void) fprintf(fp, "\n"); } } for (i = nc % BYTES_PER_LINE; i < BYTES_PER_LINE; i++) (void) fprintf(fp, " "); (void) fprintf(fp, " "); if (wide) { for (i = 0; i < nc % BYTES_PER_LINE; i += sizeof (wchar_t)) pprintwc(fp, *(wchar_t *)(d + i)); } else { for (i = 0; i < nc % BYTES_PER_LINE; i++) pprintc(fp, d[i]); } (void) fprintf(fp, "\n"); } #endif /* DEBUG */