manywarnings
mbrlen
mbrtowc
- mbsalign
mbschr
mbslen
mbswidth
stpcpy
stpncpy
strdup-posix
- strncat
+ strnlen
strnumcmp
strsignal
strtoimax
+++ /dev/null
-/* Align/Truncate a string in a given screen width
- Copyright (C) 2009-2023 Free Software Foundation, Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>. */
-
-/* Written by Pádraig Brady. */
-
-#include <config.h>
-#include "mbsalign.h"
-
-#include "minmax.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <limits.h>
-#include <wchar.h>
-#include <wctype.h>
-
-/* Replace non printable chars.
- Note \t and \n etc. are non printable.
- Return 1 if replacement made, 0 otherwise. */
-
-static bool
-wc_ensure_printable (wchar_t *wchars)
-{
- bool replaced = false;
- wchar_t *wc = wchars;
- while (*wc)
- {
- if (!iswprint ((wint_t) *wc))
- {
- *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
- replaced = true;
- }
- wc++;
- }
- return replaced;
-}
-
-/* Truncate wchar string to width cells.
- * Returns number of cells used. */
-
-static size_t
-wc_truncate (wchar_t *wc, size_t width)
-{
- size_t cells = 0;
- int next_cells = 0;
-
- while (*wc)
- {
- next_cells = wcwidth (*wc);
- if (next_cells == -1) /* non printable */
- {
- *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
- next_cells = 1;
- }
- if (cells + next_cells > width)
- break;
- cells += next_cells;
- wc++;
- }
- *wc = L'\0';
- return cells;
-}
-
-/* Write N_SPACES space characters to DEST while ensuring
- nothing is written beyond DEST_END. A terminating NUL
- is always added to DEST.
- A pointer to the terminating NUL is returned. */
-
-static char *
-mbs_align_pad (char *dest, char const *dest_end, size_t n_spaces)
-{
- /* FIXME: Should we pad with "figure space" (\u2007)
- if non ascii data present? */
- while (n_spaces-- && (dest < dest_end))
- *dest++ = ' ';
- *dest = '\0';
- return dest;
-}
-
-/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
- characters; write the result into the DEST_SIZE-byte buffer, DEST.
- ALIGNMENT specifies whether to left- or right-justify or to center.
- If SRC requires more than *WIDTH columns, truncate it to fit.
- When centering, the number of trailing spaces may be one less than the
- number of leading spaces.
- Return the length in bytes required for the final result, not counting
- the trailing NUL. A return value of DEST_SIZE or larger means there
- wasn't enough space. DEST will be NUL terminated in any case.
- Return SIZE_MAX upon error (invalid multi-byte sequence in SRC,
- or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
- Update *WIDTH to indicate how many columns were used before padding. */
-
-size_t
-mbsalign (char const *src, char *dest, size_t dest_size,
- size_t *width, mbs_align_t align, int flags)
-{
- size_t ret = SIZE_MAX;
- size_t src_size = strlen (src) + 1;
- char *newstr = nullptr;
- wchar_t *str_wc = nullptr;
- char const *str_to_print = src;
- size_t n_cols = src_size - 1;
- size_t n_used_bytes = n_cols; /* Not including NUL */
- size_t n_spaces = 0;
- bool conversion = false;
- bool wc_enabled = false;
-
- /* In multi-byte locales convert to wide characters
- to allow easy truncation. Also determine number
- of screen columns used. */
- if (!(flags & MBA_UNIBYTE_ONLY) && MB_CUR_MAX > 1)
- {
- size_t src_chars = mbstowcs (nullptr, src, 0);
- if (src_chars == SIZE_MAX)
- {
- if (flags & MBA_UNIBYTE_FALLBACK)
- goto mbsalign_unibyte;
- else
- goto mbsalign_cleanup;
- }
- src_chars += 1; /* make space for NUL */
- str_wc = malloc (src_chars * sizeof (wchar_t));
- if (str_wc == nullptr)
- {
- if (flags & MBA_UNIBYTE_FALLBACK)
- goto mbsalign_unibyte;
- else
- goto mbsalign_cleanup;
- }
- if (mbstowcs (str_wc, src, src_chars) != 0)
- {
- str_wc[src_chars - 1] = L'\0';
- wc_enabled = true;
- conversion = wc_ensure_printable (str_wc);
- n_cols = wcswidth (str_wc, src_chars);
- }
- }
-
- /* If we transformed or need to truncate the source string
- then create a modified copy of it. */
- if (wc_enabled && (conversion || (n_cols > *width)))
- {
- if (conversion)
- {
- /* May have increased the size by converting
- \t to \uFFFD for example. */
- src_size = wcstombs (nullptr, str_wc, 0) + 1;
- }
- newstr = malloc (src_size);
- if (newstr == nullptr)
- {
- if (flags & MBA_UNIBYTE_FALLBACK)
- goto mbsalign_unibyte;
- else
- goto mbsalign_cleanup;
- }
- str_to_print = newstr;
- n_cols = wc_truncate (str_wc, *width);
- n_used_bytes = wcstombs (newstr, str_wc, src_size);
- }
-
-mbsalign_unibyte:
-
- if (n_cols > *width) /* Unibyte truncation required. */
- {
- n_cols = *width;
- n_used_bytes = n_cols;
- }
-
- if (*width > n_cols) /* Padding required. */
- n_spaces = *width - n_cols;
-
- /* indicate to caller how many cells needed (not including padding). */
- *width = n_cols;
-
- {
- size_t start_spaces, end_spaces;
-
- switch (align)
- {
- case MBS_ALIGN_LEFT:
- start_spaces = 0;
- end_spaces = n_spaces;
- break;
- case MBS_ALIGN_RIGHT:
- start_spaces = n_spaces;
- end_spaces = 0;
- break;
- case MBS_ALIGN_CENTER:
- default:
- start_spaces = n_spaces / 2 + n_spaces % 2;
- end_spaces = n_spaces / 2;
- break;
- }
-
- if (flags & MBA_NO_LEFT_PAD)
- start_spaces = 0;
- if (flags & MBA_NO_RIGHT_PAD)
- end_spaces = 0;
-
- /* Write as much NUL terminated output to DEST as possible. */
- if (dest_size != 0)
- {
- size_t space_left;
- char *dest_end = dest + dest_size - 1;
-
- dest = mbs_align_pad (dest, dest_end, start_spaces);
- space_left = dest_end - dest;
- dest = mempcpy (dest, str_to_print, MIN (n_used_bytes, space_left));
- mbs_align_pad (dest, dest_end, end_spaces);
- }
-
- /* indicate to caller how many bytes needed (not including NUL). */
- ret = n_used_bytes + ((start_spaces + end_spaces) * 1);
- }
-
-mbsalign_cleanup:
-
- free (str_wc);
- free (newstr);
-
- return ret;
-}
-
-/* A wrapper around mbsalign() to dynamically allocate the
- minimum amount of memory to store the result.
- Return nullptr on failure. */
-
-char *
-ambsalign (char const *src, size_t *width, mbs_align_t align, int flags)
-{
- size_t orig_width = *width;
- size_t size = *width; /* Start with enough for unibyte mode. */
- size_t req = size;
- char *buf = nullptr;
-
- while (req >= size)
- {
- char *nbuf;
- size = req + 1; /* Space for NUL. */
- nbuf = realloc (buf, size);
- if (nbuf == nullptr)
- {
- free (buf);
- buf = nullptr;
- break;
- }
- buf = nbuf;
- *width = orig_width;
- req = mbsalign (src, buf, size, width, align, flags);
- if (req == SIZE_MAX)
- {
- free (buf);
- buf = nullptr;
- break;
- }
- }
-
- return buf;
-}
+++ /dev/null
-/* Align/Truncate a string in a given screen width
- Copyright (C) 2009-2023 Free Software Foundation, Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>. */
-
-#include <stddef.h>
-#include <stdlib.h>
-
-typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t;
-
-enum {
- /* Use unibyte mode for invalid multibyte strings
- or when heap memory is exhausted. */
- MBA_UNIBYTE_FALLBACK = 0x0001,
-
- /* As an optimization, don't do multibyte processing
- when we know no multibyte characters are present. */
- MBA_UNIBYTE_ONLY = 0x0002,
-
- /* Don't add leading padding. */
- MBA_NO_LEFT_PAD = 0x0004,
-
- /* Don't add trailing padding. */
- MBA_NO_RIGHT_PAD = 0x0008
-
-#if 0 /* Other possible options. */
- /* Skip invalid multibyte chars rather than failing. */
- MBA_IGNORE_INVALID
-
- /* Align multibyte strings using "figure space" (\u2007). */
- MBA_USE_FIGURE_SPACE
-
- /* Don't truncate. */
- MBA_NO_TRUNCATE
-
- /* Ensure no leading whitespace. */
- MBA_LSTRIP
-
- /* Ensure no trailing whitespace. */
- MBA_RSTRIP
-#endif
-};
-
-size_t
-mbsalign (char const *src, char *dest, size_t dest_size,
- size_t *width, mbs_align_t align, int flags)
- _GL_ATTRIBUTE_NONNULL ();
-
-char *
-ambsalign (char const *src, size_t *width, mbs_align_t align, int flags)
- _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE
- _GL_ATTRIBUTE_NONNULL ();
+++ /dev/null
-Description:
-Align/Truncate a string in a given screen width.
-
-Files:
-lib/mbsalign.c
-lib/mbsalign.h
-
-Depends-on:
-minmax
-wchar
-wctype
-wcwidth
-mempcpy
-stdint
-
-configure.ac:
-
-Makefile.am:
-lib_SOURCES += mbsalign.c mbsalign.h
-
-Include:
-"mbsalign.h"
-
-License:
-LGPL
-
-Maintainer:
-Pádraig Brady
+++ /dev/null
-Files:
-tests/test-mbsalign.c
-tests/macros.h
-
-Depends-on:
-
-configure.ac:
-
-Makefile.am:
-TESTS += test-mbsalign
-check_PROGRAMS += test-mbsalign
+++ /dev/null
-/* Test that mbsalign works as advertised.
- Copyright (C) 2010-2023 Free Software Foundation, Inc.
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>. */
-
-/* Written by Pádraig Brady. */
-
-#include <config.h>
-
-#include "mbsalign.h"
-#include "macros.h"
-#include <stdlib.h>
-#include <locale.h>
-
-int
-main (void)
-{
- char dest[4 * 16 + 1];
- size_t width, n;
-
-#ifdef __ANDROID__
- /* On Android ≥ 5.0, the default locale is the "C.UTF-8" locale, not the
- "C" locale. Furthermore, when you attempt to set the "C" or "POSIX"
- locale via setlocale(), what you get is a "C" locale with UTF-8 encoding,
- that is, effectively the "C.UTF-8" locale. */
- if (MB_CUR_MAX == 1)
-#endif
- {
- /* Test unibyte truncation. */
- width = 4;
- n = mbsalign ("t\tés", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == 4);
- }
-
- /* Test center alignment. */
- width = 4;
- n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0);
- ASSERT (*dest == ' ' && *(dest + n - 1) == ' ');
- ASSERT (n == 4);
-
- /* Test center alignment, with no trailing padding. */
- width = 4;
- n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER,
- MBA_NO_RIGHT_PAD);
- ASSERT (n == 3);
- ASSERT (*dest == ' ' && *(dest + n - 1) == 's');
-
- /* Test left alignment, with no trailing padding. (truncate only). */
- width = 4;
- n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_LEFT,
- MBA_NO_RIGHT_PAD);
- ASSERT (n == 2);
- ASSERT (*dest == 'e' && *(dest + n - 1) == 's');
-
- /* Test center alignment, with no padding. (truncate only). */
- width = 4;
- n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER,
- MBA_NO_LEFT_PAD | MBA_NO_RIGHT_PAD);
- ASSERT (n == 2);
- ASSERT (*dest == 'e' && *(dest + n - 1) == 's');
-
- /* Test center alignment, with no left padding. (may be useful for RTL?) */
- width = 4;
- n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER,
- MBA_NO_LEFT_PAD);
- ASSERT (n == 3);
- ASSERT (*dest == 'e' && *(dest + n - 1) == ' ');
-
- if (setlocale (LC_ALL, "en_US.UTF8"))
- {
- /* Check invalid input is flagged. */
- width = 4;
- n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == (size_t) -1);
-
- /* Check invalid input is treated as unibyte */
- width = 4;
- n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width,
- MBS_ALIGN_LEFT, MBA_UNIBYTE_FALLBACK);
- ASSERT (n == 4);
-
- /* Test multibyte center alignment. */
- width = 4;
- n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0);
- ASSERT (n == 5);
- ASSERT (*dest == ' ' && *(dest + n - 1) == ' ');
-
- /* Test multibyte left alignment. */
- width = 4;
- n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == 5);
- ASSERT (*(dest + n - 1) == ' ' && *(dest + n - 2) == ' ');
-
- /* Test multibyte right alignment. */
- width = 4;
- n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_RIGHT, 0);
- ASSERT (n == 5);
- ASSERT (*(dest) == ' ' && *(dest + 1) == ' ');
-
- /* multibyte multicell truncation. */
- width = 4; /* cells */
- n = mbsalign ("日月火水", dest, sizeof dest, &width,
- MBS_ALIGN_LEFT, 0);
- ASSERT (n == 6); /* 2 characters */
-
- /* multibyte unicell truncation. */
- width = 3; /* cells */
- n = mbsalign ("¹²³⁴", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == 6); /* 3 characters */
-
- /* Check independence from dest buffer. */
- width = 4; /* cells */
- n = mbsalign ("¹²³⁴", dest, 0, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == 9); /* 4 characters */
-
- /* Check that width is updated with cells required before padding. */
- width = 4; /* cells */
- n = mbsalign ("¹²³", dest, 0, &width, MBS_ALIGN_LEFT, 0);
- ASSERT (width == 3);
-
- /* Test case where output is larger than input
- (as tab converted to multi byte replacement char). */
- width = 4;
- n = mbsalign ("t\tés" /* 6 including NUL */ , dest, sizeof dest,
- &width, MBS_ALIGN_LEFT, 0);
- ASSERT (n == 7);
-
- /* Test forced unibyte truncation. */
- width = 4;
- n = mbsalign ("t\tés", dest, sizeof dest, &width, MBS_ALIGN_LEFT,
- MBA_UNIBYTE_ONLY);
- ASSERT (n == 4);
- }
-
- return 0;
-}
#include "canonicalize.h"
#include "fsusage.h"
#include "human.h"
-#include "mbsalign.h"
#include "mbswidth.h"
#include "mountlist.h"
#include "quote.h"
char const *arg;
field_type_t field_type;
char const *caption;/* nullptr means use default header of this field. */
- size_t width; /* Auto adjusted (up) widths used to align columns. */
- mbs_align_t align; /* Alignment for this field. */
+ int width; /* Auto adjusted (up) widths used to align columns. */
+ bool align_right; /* Whether to right-align columns, not left-align. */
bool used;
};
/* Header strings, minimum width and alignment for the above fields. */
static struct field_data_t field_data[] = {
[SOURCE_FIELD] = { SOURCE_FIELD,
- "source", OTHER_FLD, N_("Filesystem"), 14, MBS_ALIGN_LEFT, false },
+ "source", OTHER_FLD, N_("Filesystem"), 14, false, false },
[FSTYPE_FIELD] = { FSTYPE_FIELD,
- "fstype", OTHER_FLD, N_("Type"), 4, MBS_ALIGN_LEFT, false },
+ "fstype", OTHER_FLD, N_("Type"), 4, false, false },
[SIZE_FIELD] = { SIZE_FIELD,
- "size", BLOCK_FLD, N_("blocks"), 5, MBS_ALIGN_RIGHT, false },
+ "size", BLOCK_FLD, N_("blocks"), 5, true, false },
[USED_FIELD] = { USED_FIELD,
- "used", BLOCK_FLD, N_("Used"), 5, MBS_ALIGN_RIGHT, false },
+ "used", BLOCK_FLD, N_("Used"), 5, true, false },
[AVAIL_FIELD] = { AVAIL_FIELD,
- "avail", BLOCK_FLD, N_("Available"), 5, MBS_ALIGN_RIGHT, false },
+ "avail", BLOCK_FLD, N_("Available"), 5, true, false },
[PCENT_FIELD] = { PCENT_FIELD,
- "pcent", BLOCK_FLD, N_("Use%"), 4, MBS_ALIGN_RIGHT, false },
+ "pcent", BLOCK_FLD, N_("Use%"), 4, true, false },
[ITOTAL_FIELD] = { ITOTAL_FIELD,
- "itotal", INODE_FLD, N_("Inodes"), 5, MBS_ALIGN_RIGHT, false },
+ "itotal", INODE_FLD, N_("Inodes"), 5, true, false },
[IUSED_FIELD] = { IUSED_FIELD,
- "iused", INODE_FLD, N_("IUsed"), 5, MBS_ALIGN_RIGHT, false },
+ "iused", INODE_FLD, N_("IUsed"), 5, true, false },
[IAVAIL_FIELD] = { IAVAIL_FIELD,
- "iavail", INODE_FLD, N_("IFree"), 5, MBS_ALIGN_RIGHT, false },
+ "iavail", INODE_FLD, N_("IFree"), 5, true, false },
[IPCENT_FIELD] = { IPCENT_FIELD,
- "ipcent", INODE_FLD, N_("IUse%"), 4, MBS_ALIGN_RIGHT, false },
+ "ipcent", INODE_FLD, N_("IUse%"), 4, true, false },
[TARGET_FIELD] = { TARGET_FIELD,
- "target", OTHER_FLD, N_("Mounted on"), 0, MBS_ALIGN_LEFT, false },
+ "target", OTHER_FLD, N_("Mounted on"), 0, false, false },
[FILE_FIELD] = { FILE_FIELD,
- "file", OTHER_FLD, N_("File"), 0, MBS_ALIGN_LEFT, false }
+ "file", OTHER_FLD, N_("File"), 0, false, false }
};
static char const *all_args_string =
}
}
+enum { MBSWIDTH_FLAGS = MBSW_REJECT_INVALID | MBSW_REJECT_UNPRINTABLE };
+
/* Replace problematic chars with '?'.
Since only control characters are currently considered,
this should work in all encodings. */
if (col != 0)
putchar (' ');
- int flags = 0;
- if (col == ncolumns - 1) /* The last one. */
- flags = MBA_NO_RIGHT_PAD;
-
- size_t width = columns[col]->width;
- cell = ambsalign (cell, &width, columns[col]->align, flags);
- /* When ambsalign fails, output unaligned data. */
- fputs (cell ? cell : table[row][col], stdout);
- free (cell);
+ int width = mbswidth (cell, MBSWIDTH_FLAGS);
+ int fill = width < 0 ? 0 : columns[col]->width - width;
+ if (columns[col]->align_right)
+ for (; 0 < fill; fill--)
+ putchar (' ');
+ fputs (cell, stdout);
+ if (col + 1 < ncolumns)
+ for (; 0 < fill; fill--)
+ putchar (' ');
}
putchar ('\n');
}
table[nrows - 1][col] = cell;
- size_t cell_width = mbswidth (cell, 0);
+ int cell_width = mbswidth (cell, MBSWIDTH_FLAGS);
columns[col]->width = MAX (columns[col]->width, cell_width);
}
}
affirm (cell);
replace_problematic_chars (cell);
- size_t cell_width = mbswidth (cell, 0);
+ int cell_width = mbswidth (cell, MBSWIDTH_FLAGS);
columns[col]->width = MAX (columns[col]->width, cell_width);
table[nrows - 1][col] = cell;
}
#include "xstrtol.h"
#include "xstrtol-error.h"
#include "areadlink.h"
-#include "mbsalign.h"
#include "dircolors.h"
#include "xgethostname.h"
#include "c-ctype.h"
RFC3986[i] |= c_isalnum (i) || i == '~' || i == '-' || i == '.' || i == '_';
}
+enum { MBSWIDTH_FLAGS = MBSW_REJECT_INVALID | MBSW_REJECT_UNPRINTABLE };
+
/* Read the abbreviated month names from the locale, to align them
and to determine the max width of the field and to truncate names
greater than our max allowed.
variable width abbreviated months and also precomputing/caching
the names was seen to increase the performance of ls significantly. */
-/* max number of display cells to use.
- As of 2018 the abmon for Arabic has entries with width 12.
- It doesn't make much sense to support wider than this
- and locales should aim for abmon entries of width <= 5. */
-enum { MAX_MON_WIDTH = 12 };
/* abformat[RECENT][MON] is the format to use for timestamps with
recentness RECENT and month MON. */
enum { ABFORMAT_SIZE = 128 };
#ifndef HAVE_NL_LANGINFO
return false;
#else
- size_t required_mon_width = MAX_MON_WIDTH;
- size_t curr_max_width;
- do
+ int max_mon_width = 0;
+ int mon_width[12];
+ int mon_len[12];
+
+ for (int i = 0; i < 12; i++)
{
- curr_max_width = required_mon_width;
- required_mon_width = 0;
- for (int i = 0; i < 12; i++)
+ char const *abbr = nl_langinfo (ABMON_1 + i);
+ mon_len[i] = strnlen (abbr, ABFORMAT_SIZE);
+ if (mon_len[i] == ABFORMAT_SIZE)
+ return false;
+ if (strchr (abbr, '%'))
+ return false;
+ mon_width[i] = mbswidth (strcpy (abmon[i], abbr), MBSWIDTH_FLAGS);
+ if (mon_width[i] < 0)
+ return false;
+ max_mon_width = MAX (max_mon_width, mon_width[i]);
+ }
+
+ for (int i = 0; i < 12; i++)
+ {
+ int fill = max_mon_width - mon_width[i];
+ if (ABFORMAT_SIZE - mon_len[i] <= fill)
+ return false;
+ bool align_left = !isdigit (to_uchar (abmon[i][0]));
+ int fill_offset;
+ if (align_left)
+ fill_offset = mon_len[i];
+ else
{
- size_t width = curr_max_width;
- char const *abbr = nl_langinfo (ABMON_1 + i);
- if (strchr (abbr, '%'))
- return false;
- mbs_align_t alignment = isdigit (to_uchar (*abbr))
- ? MBS_ALIGN_RIGHT : MBS_ALIGN_LEFT;
- size_t req = mbsalign (abbr, abmon[i], ABFORMAT_SIZE,
- &width, alignment, 0);
- if (! (req < ABFORMAT_SIZE))
- return false;
- required_mon_width = MAX (required_mon_width, width);
+ memmove (abmon[i] + fill, abmon[i], mon_len[i]);
+ fill_offset = 0;
}
+ memset (abmon[i] + fill_offset, ' ', fill);
+ abmon[i][mon_len[i] + fill] = '\0';
}
- while (curr_max_width > required_mon_width);
return true;
#endif
char buf[LONGEST_HUMAN_READABLE + 1];
int len = mbswidth (human_readable (blocks, buf, human_output_opts,
ST_NBLOCKSIZE, output_block_size),
- 0);
+ MBSWIDTH_FLAGS);
if (block_size_width < len)
block_size_width = len;
}
int len = mbswidth (human_readable (size, buf,
file_human_output_opts,
1, file_output_block_size),
- 0);
+ MBSWIDTH_FLAGS);
if (file_size_width < len)
file_size_width = len;
}
size_t len = align_nstrftime (buf, sizeof buf, false,
&tm, localtz, 0);
if (len != 0)
- width = mbsnwidth (buf, len, 0);
+ width = mbsnwidth (buf, len, MBSWIDTH_FLAGS);
}
if (width < 0)
{
if (name)
{
- int width_gap = width - mbswidth (name, 0);
+ int name_width = mbswidth (name, MBSWIDTH_FLAGS);
+ int width_gap = name_width < 0 ? 0 : width - name_width;
int pad = MAX (0, width_gap);
dired_outstring (name);
(numeric_ids ? nullptr : getgroup (g)), g, width);
}
-/* Return the number of columns that format_user_or_group will print. */
+/* Return the number of columns that format_user_or_group will print,
+ or -1 if unknown. */
static int
format_user_or_group_width (char const *name, uintmax_t id)
{
- if (name)
- {
- int len = mbswidth (name, 0);
- return MAX (0, len);
- }
- else
- return snprintf (nullptr, 0, "%"PRIuMAX, id);
+ return (name
+ ? mbswidth (name, MBSWIDTH_FLAGS)
+ : snprintf (nullptr, 0, "%"PRIuMAX, id));
}
-/* Return the number of columns that format_user will print. */
+/* Return the number of columns that format_user will print,
+ or -1 if unknown. */
static int
format_user_width (uid_t u)
? "?"
: human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts,
ST_NBLOCKSIZE, output_block_size));
- int pad;
- for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--)
+ int blocks_width = mbswidth (blocks, MBSWIDTH_FLAGS);
+ for (int pad = blocks_width < 0 ? 0 : block_size_width - blocks_width;
+ 0 < pad; pad--)
*p++ = ' ';
while ((*p++ = *blocks++))
continue;
: human_readable (unsigned_file_size (f->stat.st_size),
hbuf, file_human_output_opts, 1,
file_output_block_size));
- int pad;
- for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--)
+ int size_width = mbswidth (size, MBSWIDTH_FLAGS);
+ for (int pad = size_width < 0 ? 0 : block_size_width - size_width;
+ 0 < pad; pad--)
*p++ = ' ';
while ((*p++ = *size++))
continue;
else if (width != nullptr)
{
if (MB_CUR_MAX > 1)
- displayed_width = mbsnwidth (buf, len, 0);
+ {
+ displayed_width = mbsnwidth (buf, len, MBSWIDTH_FLAGS);
+ displayed_width = MAX (0, displayed_width);
+ }
else
{
char const *p = buf;
#include <config.h>
#include <float.h>
#include <getopt.h>
+#include <stdckdint.h>
#include <stdio.h>
#include <sys/types.h>
#include <langinfo.h>
-#include "mbsalign.h"
#include "argmatch.h"
#include "c-ctype.h"
+#include "mbswidth.h"
#include "quote.h"
#include "system.h"
#include "xstrtol.h"
static uintmax_t to_unit_size = 1;
static int grouping = 0;
static char *padding_buffer = nullptr;
-static size_t padding_buffer_size = 0;
-static long int padding_width = 0;
-static long int zero_padding_width = 0;
+static idx_t padding_buffer_size = 0;
+static intmax_t padding_width = 0;
+static int zero_padding_width = 0;
static long int user_precision = -1;
static char const *format_str = nullptr;
static char *format_str_prefix = nullptr;
/* auto-pad each line based on skipped whitespace. */
static int auto_padding = 0;
-static mbs_align_t padding_alignment = MBS_ALIGN_RIGHT;
/* field delimiter */
static int delimiter = DELIMITER_DEFAULT;
error (conv_exit_code, 0, gettext (msgid), quote (input_str));
}
-/* Convert VAL to a human format string in BUF. */
-static void
+/* Convert VAL to a human format string using PRECISION in BUF of size
+ BUF_SIZE. Use SCALE, GROUP, and ROUND to format. Return
+ the number of bytes needed to represent VAL. If this number is not
+ less than BUF_SIZE, the buffer is too small; if it is negative, the
+ formatting failed for some reason. */
+static int
double_to_human (long double val, int precision,
- char *buf, size_t buf_size,
+ char *buf, idx_t buf_size,
enum scale_type scale, int group, enum round_type round)
{
- int num_size;
- char fmt[64];
- static_assert ((INT_BUFSIZE_BOUND (zero_padding_width)
- + INT_BUFSIZE_BOUND (precision)
- + 10 /* for %.Lf etc. */)
- < sizeof fmt);
-
+ char fmt[sizeof "%'0.*Lfi%s%s%s" + INT_STRLEN_BOUND (zero_padding_width)];
char *pfmt = fmt;
*pfmt++ = '%';
*pfmt++ = '\'';
if (zero_padding_width)
- pfmt += snprintf (pfmt, sizeof (fmt) - 2, "0%ld", zero_padding_width);
+ pfmt += sprintf (pfmt, "0%d", zero_padding_width);
devmsg ("double_to_human:\n");
" no scaling, returning (grouped) value: %'.*Lf\n" :
" no scaling, returning value: %.*Lf\n", precision, val);
- stpcpy (pfmt, ".*Lf");
+ strcpy (pfmt, ".*Lf%s");
- num_size = snprintf (buf, buf_size, fmt, precision, val);
- if (num_size < 0 || num_size >= (int) buf_size)
- error (EXIT_FAILURE, 0,
- _("failed to prepare value '%Lf' for printing"), val);
- return;
+ return snprintf (buf, buf_size, fmt, precision, val,
+ suffix ? suffix : "");
}
/* Scaling requested by user. */
devmsg (" after rounding, value=%Lf * %0.f ^ %d\n", val, scale_base, power);
- stpcpy (pfmt, ".*Lf%s");
+ strcpy (pfmt, ".*Lf%s%s%s");
int prec = user_precision == -1 ? show_decimal_point : user_precision;
- /* buf_size - 1 used here to ensure place for possible scale_IEC_I suffix. */
- num_size = snprintf (buf, buf_size - 1, fmt, prec, val,
- suffix_power_char (power));
- if (num_size < 0 || num_size >= (int) buf_size - 1)
- error (EXIT_FAILURE, 0,
- _("failed to prepare value '%Lf' for printing"), val);
-
- if (scale == scale_IEC_I && power > 0)
- strncat (buf, "i", buf_size - num_size - 1);
-
- devmsg (" returning value: %s\n", quote (buf));
-
- return;
+ return snprintf (buf, buf_size, fmt, prec, val,
+ suffix_power_char (power),
+ &"i"[! (scale == scale_IEC_I && 0 < power)],
+ suffix ? suffix : "");
}
/* Convert a string of decimal digits, N_STRING, with an optional suffix
return n;
}
-
-static void
-setup_padding_buffer (size_t min_size)
-{
- if (padding_buffer_size > min_size)
- return;
-
- padding_buffer_size = min_size + 1;
- padding_buffer = xrealloc (padding_buffer, padding_buffer_size);
-}
-
void
usage (int status)
{
NOTES:
1. This function sets the global variables:
- padding_width, padding_alignment, grouping,
+ padding_width, grouping,
format_str_prefix, format_str_suffix
2. The function aborts on any errors. */
static void
size_t i;
size_t prefix_len = 0;
size_t suffix_pos;
- long int pad = 0;
char *endptr = nullptr;
bool zero_padding = false;
break;
}
- errno = 0;
- pad = strtol (fmt + i, &endptr, 10);
- if (errno == ERANGE || pad < -LONG_MAX)
- error (EXIT_FAILURE, 0,
- _("invalid format %s (width overflow)"), quote (fmt));
+ intmax_t pad = strtoimax (fmt + i, &endptr, 10);
- if (endptr != (fmt + i) && pad != 0)
+ if (pad != 0)
{
if (debug && padding_width && !(zero_padding && pad > 0))
error (0, 0, _("--format padding overriding --padding"));
+ /* Set padding width and alignment. On overflow, set widths to
+ large values that cause later code to avoid undefined behavior
+ and fail at a reasonable point. */
if (pad < 0)
- {
- padding_alignment = MBS_ALIGN_LEFT;
- padding_width = -pad;
- }
+ padding_width = pad;
else
{
if (zero_padding)
- zero_padding_width = pad;
+ zero_padding_width = MIN (pad, INT_MAX);
else
padding_width = pad;
}
-
}
i = endptr - fmt;
format_str_suffix = xstrdup (fmt + suffix_pos);
devmsg ("format String:\n input: %s\n grouping: %s\n"
- " padding width: %ld\n alignment: %s\n"
+ " padding width: %jd\n"
" prefix: %s\n suffix: %s\n",
quote_n (0, fmt), (grouping) ? "yes" : "no",
padding_width,
- (padding_alignment == MBS_ALIGN_LEFT) ? "Left" : "Right",
quote_n (1, format_str_prefix ? format_str_prefix : ""),
quote_n (2, format_str_suffix ? format_str_suffix : ""));
}
/* Print the given VAL, using the requested representation.
The number is printed to STDOUT, with padding and alignment. */
-static int
-prepare_padded_number (const long double val, size_t precision)
+static bool
+prepare_padded_number (const long double val, size_t precision,
+ intmax_t *padding)
{
/* Generate Output. */
- char buf[128];
-
size_t precision_used = user_precision == -1 ? precision : user_precision;
/* Can't reliably print too-large values without auto-scaling. */
_("value too large to be printed: '%Lg'"
" (consider using --to)"), val);
}
- return 0;
+ return false;
}
if (x > MAX_ACCEPTABLE_DIGITS - 1)
if (inval_style != inval_ignore)
error (conv_exit_code, 0, _("value too large to be printed: '%Lg'"
" (cannot handle values > 999Q)"), val);
- return 0;
+ return false;
}
- double_to_human (val, precision_used, buf, sizeof (buf),
- scale_to, grouping, round_style);
- if (suffix)
- strncat (buf, suffix, sizeof (buf) - strlen (buf) -1);
+ while (true)
+ {
+ int numlen = double_to_human (val, precision_used,
+ padding_buffer, padding_buffer_size,
+ scale_to, grouping, round_style);
+ ptrdiff_t growth;
+ if (numlen < 0 || ckd_sub (&growth, numlen, padding_buffer_size - 1))
+ error (EXIT_FAILURE, 0,
+ _("failed to prepare value '%Lf' for printing"), val);
+ if (growth <= 0)
+ break;
+ padding_buffer = xpalloc (padding_buffer, &padding_buffer_size,
+ growth, -1, 1);
+ }
devmsg ("formatting output:\n value: %Lf\n humanized: %s\n",
- val, quote (buf));
+ val, quote (padding_buffer));
- if (padding_width && strlen (buf) < padding_width)
+ intmax_t pad = 0;
+ if (padding_width)
{
- size_t w = padding_width;
- mbsalign (buf, padding_buffer, padding_buffer_size, &w,
- padding_alignment, MBA_UNIBYTE_ONLY);
-
- devmsg (" After padding: %s\n", quote (padding_buffer));
- }
- else
- {
- setup_padding_buffer (strlen (buf) + 1);
- strcpy (padding_buffer, buf);
+ int buf_width = mbswidth (padding_buffer,
+ MBSW_REJECT_INVALID | MBSW_REJECT_UNPRINTABLE);
+ if (0 <= buf_width)
+ {
+ if (padding_width < 0)
+ {
+ if (padding_width < -buf_width)
+ pad = padding_width + buf_width;
+ }
+ else
+ {
+ if (buf_width < padding_width)
+ pad = padding_width - buf_width;
+ }
+ }
}
- return 1;
+ *padding = pad;
+ return true;
}
static void
-print_padded_number (void)
+print_padded_number (intmax_t padding)
{
if (format_str_prefix)
fputs (format_str_prefix, stdout);
+ for (intmax_t p = padding; 0 < p; p--)
+ putchar (' ');
+
fputs (padding_buffer, stdout);
+ for (intmax_t p = padding; p < 0; p++)
+ putchar (' ');
+
if (format_str_suffix)
fputs (format_str_suffix, stdout);
}
/* setup auto-padding. */
if (auto_padding)
{
- if (text < p || field > 1)
- {
- padding_width = strlen (text);
- setup_padding_buffer (padding_width);
- }
- else
- {
- padding_width = 0;
- }
- devmsg ("setting Auto-Padding to %ld characters\n", padding_width);
+ padding_width = text < p || 1 < field ? strlen (text) : 0;
+ devmsg ("setting Auto-Padding to %jd characters\n", padding_width);
}
long double val = 0;
valid_number =
process_suffixed_number (text, &val, &precision, field);
+ intmax_t padding;
if (valid_number)
- valid_number = prepare_padded_number (val, precision);
+ valid_number = prepare_padded_number (val, precision, &padding);
if (valid_number)
- print_padded_number ();
+ print_padded_number (padding);
else
fputs (text, stdout);
}
break;
case PADDING_OPTION:
- if (xstrtol (optarg, nullptr, 10, &padding_width, "") != LONGINT_OK
- || padding_width == 0 || padding_width < -LONG_MAX)
+ if (((xstrtoimax (optarg, nullptr, 10, &padding_width, "")
+ & ~LONGINT_OVERFLOW)
+ != LONGINT_OK)
+ || padding_width == 0)
error (EXIT_FAILURE, 0, _("invalid padding value %s"),
quote (optarg));
- if (padding_width < 0)
- {
- padding_alignment = MBS_ALIGN_LEFT;
- padding_width = -padding_width;
- }
/* TODO: We probably want to apply a specific --padding
to --header lines too. */
break;
error (0, 0, _("grouping has no effect in this locale"));
}
-
- setup_padding_buffer (padding_width);
auto_padding = (padding_width == 0 && delimiter == DELIMITER_DEFAULT);
if (inval_style != inval_abort)
# space(s) between number and suffix. Note only field 1 is used
# by default so specify the NUL delimiter to consider the whole "line".
['suf-19', "-d '' --from=si '4.0 K'", {OUT => "4000"}],
+ ['suf-20',
+ '--suffix=Fooxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy 0',
+ {OUT => "0Fooxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxy"}],
## GROUPING
['pad-3.1', '--padding=0 5',
{ERR => "$prog: invalid padding value '0'\n"},
{EXIT => '1'}],
- ['pad-3.2', "--padding=$limits->{LONG_MIN} 0",
- {ERR => "$prog: invalid padding value '$limits->{LONG_MIN}'\n"},
- {EXIT => '1'}],
['pad-4', '--padding=10 --to=si 50000', {OUT=>' 50K'}],
['pad-5', '--padding=-10 --to=si 50000', {OUT=>'50K '}],
['fmt-err-6', '--format "%f %f"',
{ERR=>"$prog: format '%f %f' has too many % directives\n"},
{EXIT=>1}],
- ['fmt-err-7', '--format "%'.$limits->{LONG_OFLOW}.'f"',
- {ERR=>"$prog: invalid format '%$limits->{LONG_OFLOW}f'".
- " (width overflow)\n"},
- {EXIT=>1}],
['fmt-err-9', '--format "%f" --grouping',
{ERR=>"$prog: --grouping cannot be combined with --format\n"},
{EXIT=>1}],