1 /* $NetBSD: h_sort.c,v 1.3 2025/03/02 23:11:19 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2025 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __RCSID("$NetBSD: h_sort.c,v 1.3 2025/03/02 23:11:19 riastradh Exp $"); 31 32 #include <assert.h> 33 #include <err.h> 34 #include <getopt.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 static void 41 heapsort_r_wrapper(void *a, size_t n, size_t sz, 42 int (*cmp)(const void *, const void *, void *), void *cookie) 43 { 44 45 if (heapsort_r(a, n, sz, cmp, cookie) == -1) 46 err(1, "heapsort_r"); 47 } 48 49 static void 50 mergesort_r_wrapper(void *a, size_t n, size_t sz, 51 int (*cmp)(const void *, const void *, void *), void *cookie) 52 { 53 54 if (mergesort_r(a, n, sz, cmp, cookie) == -1) 55 err(1, "mergesort_r"); 56 } 57 58 struct context { 59 const char *buf; 60 const size_t *linepos; 61 }; 62 63 static int 64 cmp(const void *va, const void *vb, void *cookie) 65 { 66 const struct context *C = cookie; 67 const size_t *a = va; 68 const size_t *b = vb; 69 70 return strcmp(C->buf + C->linepos[*a], C->buf + C->linepos[*b]); 71 } 72 73 static void __dead 74 usage(void) 75 { 76 77 fprintf(stderr, "Usage: %s [-n] <sortfn>\n", getprogname()); 78 exit(1); 79 } 80 81 int 82 main(int argc, char **argv) 83 { 84 int ch; 85 int nflag = 0; 86 void (*sortfn)(void *, size_t, size_t, 87 int (*)(const void *, const void *, void *), void *); 88 char *buf = NULL; 89 size_t nbuf; 90 size_t *linepos = NULL; 91 size_t nlines; 92 size_t *permutation = NULL; 93 size_t off; 94 ssize_t nread; 95 char *p; 96 size_t i; 97 int error; 98 99 /* 100 * Parse arguments. 101 */ 102 setprogname(argv[0]); 103 while ((ch = getopt(argc, argv, "hn")) != -1) { 104 switch (ch) { 105 case 'n': 106 nflag = 1; 107 break; 108 case '?': 109 case 'h': 110 default: 111 usage(); 112 } 113 } 114 argc -= optind; 115 argv += optind; 116 if (argc != 1) 117 usage(); 118 if (strcmp(argv[0], "heapsort_r") == 0) 119 sortfn = &heapsort_r_wrapper; 120 else if (strcmp(argv[0], "mergesort_r") == 0) 121 sortfn = &mergesort_r_wrapper; 122 else if (strcmp(argv[0], "qsort_r") == 0) 123 sortfn = &qsort_r; 124 else 125 errx(1, "unknown sort: %s", argv[0]); 126 127 /* 128 * Allocate an initial 4K buffer. 129 */ 130 nbuf = 0x1000; 131 error = reallocarr(&buf, nbuf, 1); 132 if (error) 133 errc(1, error, "reallocarr"); 134 135 /* 136 * Read the input into a contiguous buffer. Reject input with 137 * embedded NULs so we can use strcmp(3) to compare lines. 138 */ 139 off = 0; 140 while ((nread = read(STDIN_FILENO, buf + off, nbuf - off - 1)) != 0) { 141 if (nread == -1) 142 err(1, "read"); 143 if ((size_t)nread > nbuf - off - 1) 144 errx(1, "overlong read: %zu", (size_t)nread); 145 if (memchr(buf + off, '\0', (size_t)nread) != NULL) 146 errx(1, "NUL byte in input"); 147 off += (size_t)nread; 148 149 /* 150 * If we filled the buffer, reallocate it with double 151 * the size. Bail if that would overflow. 152 */ 153 if (nbuf - off == 1) { 154 if (nbuf > SIZE_MAX/2) 155 errx(1, "input overflow"); 156 nbuf *= 2; 157 error = reallocarr(&buf, nbuf, 1); 158 if (error) 159 errc(1, error, "reallocarr"); 160 } 161 } 162 163 /* 164 * If the input was empty, nothing to do. 165 */ 166 if (off == 0) 167 return 0; 168 169 /* 170 * NUL-terminate the input and count the lines. The last line 171 * may have no trailing \n. 172 */ 173 buf[off] = '\0'; 174 nlines = 1; 175 for (p = buf; (p = strchr(p, '\n')) != NULL;) { 176 if (*++p == '\0') 177 break; 178 nlines++; 179 } 180 181 /* 182 * Create an array of line positions to sort. NUL-terminate 183 * each line so we can use strcmp(3). 184 */ 185 error = reallocarr(&linepos, nlines, sizeof(linepos[0])); 186 if (error) 187 errc(1, error, "reallocarr"); 188 i = 0; 189 for (p = buf; linepos[i++] = p - buf, (p = strchr(p, '\n')) != NULL;) { 190 *p = '\0'; 191 if (*++p == '\0') 192 break; 193 } 194 assert(i == nlines); 195 196 /* 197 * Create an array of permuted line numbers. 198 */ 199 error = reallocarr(&permutation, nlines, sizeof(permutation[0])); 200 if (error) 201 errc(1, error, "reallocarr"); 202 for (i = 0; i < nlines; i++) 203 permutation[i] = i; 204 205 /* 206 * Sort the lines via comparison function that consults the 207 * buffer as a cookie. 208 */ 209 (*sortfn)(permutation, nlines, sizeof(permutation[0]), &cmp, 210 &(struct context) { .buf = buf, .linepos = linepos }); 211 212 /* 213 * Print the lines in sorted order with the original line 214 * numbers. 215 */ 216 for (i = 0; i < nlines; i++) { 217 const size_t j = permutation[i]; 218 if (nflag) 219 printf("%zu %s\n", j, buf + linepos[j]); 220 else 221 printf("%s\n", buf + linepos[j]); 222 } 223 fflush(stdout); 224 return ferror(stdout); 225 } 226