static void sortlines_temp (struct line *, size_t, struct line *);
+/* Report MESSAGE for FILE, then clean up and exit.
+ If FILE is null, it represents standard output. */
+
+static void die (char const *, char const *) ATTRIBUTE_NORETURN;
+static void
+die (char const *message, char const *file)
+{
+ error (0, errno, "%s: %s", message, file ? file : _("standard output"));
+ exit (SORT_FAILURE);
+}
+
void
usage (int status)
{
char name[1]; /* Actual size is 1 + file name length. */
};
static struct tempnode *volatile temphead;
+static struct tempnode *volatile *temptail = &temphead;
-/* Clean up any remaining temporary files. */
+/* Clean up any remaining temporary files. */
static void
cleanup (void)
unlink (node->name);
}
-/* Report MESSAGE for FILE, then clean up and exit.
- If FILE is null, it represents standard output. */
-
-static void die (char const *, char const *) ATTRIBUTE_NORETURN;
-static void
-die (char const *message, char const *file)
-{
- error (0, errno, "%s: %s", message, file ? file : _("standard output"));
- exit (SORT_FAILURE);
-}
-
/* Create a new temporary file, returning its newly allocated name.
Store into *PFP a stream open for writing. */
char const *temp_dir = temp_dirs[temp_dir_index];
size_t len = strlen (temp_dir);
struct tempnode *node =
- xmalloc (sizeof node->next + len + sizeof slashbase);
+ xmalloc (offsetof (struct tempnode, name) + len + sizeof slashbase);
char *file = node->name;
memcpy (file, temp_dir, len);
memcpy (file + len, slashbase, sizeof slashbase);
- node->next = temphead;
+ node->next = NULL;
if (++temp_dir_index == temp_dir_count)
temp_dir_index = 0;
sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
fd = mkstemp (file);
if (0 <= fd)
- temphead = node;
+ {
+ *temptail = node;
+ temptail = &node->next;
+ }
saved_errno = errno;
sigprocmask (SIG_SETMASK, &oldset, NULL);
errno = saved_errno;
{
struct tempnode *volatile *pnode;
struct tempnode *node;
+ sigset_t oldset;
for (pnode = &temphead; (node = *pnode); pnode = &node->next)
if (node->name == name)
{
+ /* Unlink the temporary file in a critical section, to avoid races. */
+ sigprocmask (SIG_BLOCK, &caught_signals, &oldset);
unlink (name);
- *pnode = node->next;
+ if (! (*pnode = node->next))
+ temptail = pnode;
+ sigprocmask (SIG_SETMASK, &oldset, NULL);
free (node);
break;
}
return ordered;
}
-/* Merge lines from FILES onto OFP. NFILES cannot be greater than
- NMERGE. Close input and output files before returning.
+/* Merge lines from FILES onto OFP. NTEMPS is the number of temporary
+ files (all of which are at the start of the FILES array), and
+ NFILES is the number of files; 0 <= NTEMPS <= NFILES <= NMERGE.
+ Close input and output files before returning.
OUTPUT_FILE gives the name of the output file. If it is NULL,
the output file is standard output. If OFP is NULL, the output
file has not been opened yet (or written to, if standard output). */
static void
-mergefps (char **files, size_t nfiles, FILE *ofp, char const *output_file)
+mergefps (char **files, size_t ntemps, size_t nfiles,
+ FILE *ofp, char const *output_file)
{
FILE *fps[NMERGE]; /* Input streams for each file. */
struct buffer buffer[NMERGE]; /* Input buffers for each file. */
{
/* fps[i] is empty; eliminate it from future consideration. */
xfclose (fps[i], files[i]);
- zaptemp (files[i]);
+ if (i < ntemps)
+ {
+ ntemps--;
+ zaptemp (files[i]);
+ }
free (buffer[i].buf);
--nfiles;
for (j = i; j < nfiles; ++j)
--ord[i];
--nfiles;
xfclose (fps[ord[0]], files[ord[0]]);
- zaptemp (files[ord[0]]);
+ if (ord[0] < ntemps)
+ {
+ ntemps--;
+ zaptemp (files[ord[0]]);
+ }
free (buffer[ord[0]].buf);
for (i = ord[0]; i < nfiles; ++i)
{
}
}
-/* Scan through FILES[N_TEMP_FILES .. NFILES-1] looking for a file that is
+/* Scan through FILES[NTEMPS .. NFILES-1] looking for a file that is
the same as OUTFILE. If found, merge the found instances (and perhaps
some other files) into a temporary file so that it can in turn be
merged into OUTFILE without destroying OUTFILE before it is completely
common cases. */
static size_t
-avoid_trashing_input (char **files, size_t n_temp_files, size_t nfiles,
+avoid_trashing_input (char **files, size_t ntemps, size_t nfiles,
char const *outfile)
{
size_t i;
bool got_outstat = false;
struct stat outstat;
- for (i = n_temp_files; i < nfiles; i++)
+ for (i = ntemps; i < nfiles; i++)
{
bool standard_input = STREQ (files[i], "-");
bool same;
{
FILE *tftp;
char *temp = create_temp_file (&tftp);
- mergefps (&files[i], nfiles - i, tftp, temp);
+ mergefps (&files[i], 0, nfiles - i, tftp, temp);
files[i] = temp;
return i + 1;
}
return nfiles;
}
-/* Merge the input FILES. N_TEMP_FILES is the number of files at the
+/* Merge the input FILES. NTEMPS is the number of files at the
start of FILES that are temporary; it is zero at the top level.
NFILES is the total number of files. Put the output in
OUTPUT_FILE; a null OUTPUT_FILE stands for standard output. */
static void
-merge (char **files, size_t n_temp_files, size_t nfiles,
- char const *output_file)
+merge (char **files, size_t ntemps, size_t nfiles, char const *output_file)
{
while (NMERGE < nfiles)
{
{
FILE *tfp;
char *temp = create_temp_file (&tfp);
- mergefps (&files[in], NMERGE, tfp, temp);
+ size_t nt = MIN (ntemps, NMERGE);
+ ntemps -= nt;
+ mergefps (&files[in], nt, NMERGE, tfp, temp);
files[out] = temp;
}
/* So many files remain that they can't all be put into the last
NMERGE-sized output window. Do one more merge. Merge as few
files as possible, to avoid needless I/O. */
- size_t n_shortmerge = remainder - cheap_slots + 1;
+ size_t nshortmerge = remainder - cheap_slots + 1;
FILE *tfp;
char *temp = create_temp_file (&tfp);
- mergefps (&files[in], n_shortmerge, tfp, temp);
+ size_t nt = MIN (ntemps, nshortmerge);
+ ntemps -= nt;
+ mergefps (&files[in], nt, nshortmerge, tfp, temp);
files[out++] = temp;
- in += n_shortmerge;
+ in += nshortmerge;
}
/* Put the remaining input files into the last NMERGE-sized output
window, so they will be merged in the next pass. */
memmove(&files[out], &files[in], (nfiles - in) * sizeof *files);
- n_temp_files = out + (in < n_temp_files ? n_temp_files - in : 0);
+ ntemps += out;
nfiles -= in - out;
}
- nfiles = avoid_trashing_input (files, n_temp_files, nfiles, output_file);
- mergefps (files, nfiles, NULL, output_file);
+ nfiles = avoid_trashing_input (files, ntemps, nfiles, output_file);
+ mergefps (files, ntemps, nfiles, NULL, output_file);
}
/* Sort NFILES FILES onto OUTPUT_FILE. */
sort (char * const *files, size_t nfiles, char const *output_file)
{
struct buffer buf;
- size_t n_temp_files = 0;
+ size_t ntemps = 0;
bool output_file_created = false;
buf.alloc = 0;
linebase = line - buf.nlines;
if (1 < buf.nlines)
sortlines (line, buf.nlines, linebase);
- if (buf.eof && !nfiles && !n_temp_files && !buf.left)
+ if (buf.eof && !nfiles && !ntemps && !buf.left)
{
xfclose (fp, file);
tfp = xfopen (output_file, "w");
}
else
{
- ++n_temp_files;
+ ++ntemps;
temp_output = create_temp_file (&tfp);
}
if (! output_file_created)
{
- size_t i = n_temp_files;
- struct tempnode *node;
- char **tempfiles = xnmalloc (n_temp_files, sizeof *tempfiles);
- for (node = temphead; i > 0; node = node->next)
- tempfiles[--i] = node->name;
- merge (tempfiles, n_temp_files, n_temp_files, output_file);
+ size_t i;
+ struct tempnode *node = temphead;
+ char **tempfiles = xnmalloc (ntemps, sizeof *tempfiles);
+ for (i = 0; node; i++)
+ {
+ tempfiles[i] = node->name;
+ node = node->next;
+ }
+ merge (tempfiles, ntemps, ntemps, output_file);
free (tempfiles);
}
}