Index: include/xlocale/_locale.h =================================================================== --- include/xlocale/_locale.h +++ include/xlocale/_locale.h @@ -43,6 +43,7 @@ #define LC_MESSAGES_MASK (1<<5) #define LC_ALL_MASK (LC_COLLATE_MASK | LC_CTYPE_MASK | LC_MESSAGES_MASK | \ LC_MONETARY_MASK | LC_NUMERIC_MASK | LC_TIME_MASK) +#define LC_VERSION_MASK (1<<6) #define LC_GLOBAL_LOCALE ((locale_t)-1) #ifndef _LOCALE_T_DEFINED Index: lib/libc/locale/collate.h =================================================================== --- lib/libc/locale/collate.h +++ lib/libc/locale/collate.h @@ -53,7 +53,9 @@ #endif #define COLLATE_STR_LEN 24 /* should be 64-bit multiple */ -#define COLLATE_VERSION "BSD 1.0\n" + +#define COLLATE_FMT_VERSION_LEN 12 +#define COLLATE_FMT_VERSION "BSD 1.0\n" #define COLLATE_MAX_PRIORITY (0x7fffffff) /* max signed value */ #define COLLATE_SUBST_PRIORITY (0x40000000) /* bit indicates subst table */ @@ -69,7 +71,8 @@ /* * The collate file format is as follows: * - * char version[COLLATE_STR_LEN]; // must be COLLATE_VERSION + * char fmt_version[COLLATE_FMT_VERSION_LEN]; // must be COLLATE_FMT_VERSION + * char def_version[XLOCALE_DEF_VERSION_LEN]; // NUL-terminated, may be empty * collate_info_t info; // see below, includes padding * collate_char_pri_t char_data[256]; // 8 bit char values * collate_subst_t subst[*]; // 0 or more substitutions Index: lib/libc/locale/collate.c =================================================================== --- lib/libc/locale/collate.c +++ lib/libc/locale/collate.c @@ -140,7 +140,9 @@ (void) _close(fd); return (_LDP_ERROR); } - if (sbuf.st_size < (COLLATE_STR_LEN + sizeof (info))) { + if (sbuf.st_size < (COLLATE_FMT_VERSION_LEN + + XLOCALE_DEF_VERSION_LEN + + sizeof (info))) { (void) _close(fd); errno = EINVAL; return (_LDP_ERROR); @@ -151,12 +153,14 @@ return (_LDP_ERROR); } - if (strncmp(TMP, COLLATE_VERSION, COLLATE_STR_LEN) != 0) { + if (strncmp(TMP, COLLATE_FMT_VERSION, COLLATE_FMT_VERSION_LEN) != 0) { (void) munmap(map, sbuf.st_size); errno = EINVAL; return (_LDP_ERROR); } - TMP += COLLATE_STR_LEN; + TMP += COLLATE_FMT_VERSION_LEN; + strlcat(table->header.version, TMP, sizeof (table->header.version)); + TMP += XLOCALE_DEF_VERSION_LEN; info = (void *)TMP; TMP += sizeof (*info); Index: lib/libc/locale/querylocale.3 =================================================================== --- lib/libc/locale/querylocale.3 +++ lib/libc/locale/querylocale.3 @@ -27,12 +27,12 @@ .\" .\" $FreeBSD$ .\" -.Dd May 3, 2013 +.Dd November 8, 2020 .Dt QUERYLOCALE 3 .Os .Sh NAME .Nm querylocale -.Nd Look up the locale name for a specified category +.Nd Look up the locale name or version for a specified category .Sh LIBRARY .Lb libc .Sh SYNOPSIS @@ -40,11 +40,22 @@ .Ft const char * .Fn querylocale "int mask" "locale_t locale" .Sh DESCRIPTION -Returns the name of the locale for the category specified by +Returns the name or version of the locale for the category specified by .Fa mask . -This possible values for the mask are the same as those in -.Xr newlocale 3 . -If more than one bit in the mask is set, the returned value is undefined. +The possible values for the mask are the same as those in +.Xr newlocale 3 , +when requesting the locale name. +Specify the bitwise OR of +.Fa LC_VERSION_MASK +and another mask value to request a version string. +Version strings can be compared to detect changes to the locale's definition. +The structure of the version string is unspecified. +Currently, version information is only available for +.Fa LC_COLLATE_MASK , +and an empty string is returned for other categories. +If more than one bit in the mask is set, not counting +.Fa LC_VERSION_MASK , +the returned value is undefined. .Sh SEE ALSO .Xr duplocale 3 , .Xr freelocale 3 , @@ -52,3 +63,12 @@ .Xr newlocale 3 , .Xr uselocale 3 , .Xr xlocale 3 +.Sh HISTORY +The +.Fn querylocale +function first appeared in +.Fx 9.1 , +and is based on the function of the same name in Darwin. +.Fa LC_VERSION_MASK +first appeared in +.Fx 13.0 . Index: lib/libc/locale/xlocale.c =================================================================== --- lib/libc/locale/xlocale.c +++ lib/libc/locale/xlocale.c @@ -231,6 +231,8 @@ if (new->components[type]) { strncpy(new->components[type]->locale, src->locale, ENCODING_LEN); + strncpy(new->components[type]->version, src->version, + XLOCALE_DEF_VERSION_LEN); } } else if (base->components[type]) { new->components[type] = xlocale_retain(base->components[type]); @@ -346,17 +348,24 @@ } /* - * Returns the name of the locale for a particular component of a locale_t. + * Returns the name or version of the locale for a particular component of a + * locale_t. */ const char *querylocale(int mask, locale_t loc) { - int type = ffs(mask) - 1; + int type = ffs(mask & ~LC_VERSION_MASK) - 1; FIX_LOCALE(loc); if (type >= XLC_LAST) return (NULL); - if (loc->components[type]) - return (loc->components[type]->locale); - return ("C"); + if (mask & LC_VERSION_MASK) { + if (loc->components[type]) + return (loc->components[type]->version); + return (""); + } else { + if (loc->components[type]) + return (loc->components[type]->locale); + return ("C"); + } } /* Index: lib/libc/locale/xlocale_private.h =================================================================== --- lib/libc/locale/xlocale_private.h +++ lib/libc/locale/xlocale_private.h @@ -91,6 +91,9 @@ /** Function used to destroy this component, if one is required*/ void(*destructor)(void*); }; + +#define XLOCALE_DEF_VERSION_LEN 12 + /** * Header for a locale component. All locale components must begin with this * header. @@ -99,6 +102,8 @@ struct xlocale_refcounted header; /** Name of the locale used for this component. */ char locale[ENCODING_LEN+1]; + /** Version of the definition for this component. */ + char version[XLOCALE_DEF_VERSION_LEN]; }; /** Index: share/colldef/Makefile =================================================================== --- share/colldef/Makefile +++ share/colldef/Makefile @@ -7,10 +7,13 @@ .SUFFIXES: .src .LC_COLLATE MAPLOC= ${.CURDIR}/../../tools/tools/locale/etc/final-maps +CLDR_VERSION= "34.0" + .include .src.LC_COLLATE: localedef ${LOCALEDEF_ENDIAN} -D -U -i ${.IMPSRC} \ + -V ${CLDR_VERSION} \ -f ${MAPLOC}/map.${.TARGET:T:R:E:C/@.*//} ${.OBJDIR}/${.IMPSRC:T:R} LOCALES+= af_ZA.UTF-8 @@ -227,6 +230,7 @@ FILESDIR_$t.LC_COLLATE= ${LOCALEDIR}/$t $t.LC_COLLATE: ${.CURDIR}/$f.src localedef ${LOCALEDEF_ENDIAN} -D -U -i ${.ALLSRC} \ + -V ${CLDR_VERSION} \ -f ${MAPLOC}/map.${.TARGET:T:R:E:C/@.*//} \ ${.OBJDIR}/${.TARGET:T:R} .endfor Index: tools/tools/locale/Makefile =================================================================== --- tools/tools/locale/Makefile +++ tools/tools/locale/Makefile @@ -187,6 +187,8 @@ cd ${UNIDIR} && unzip -o ../${CLDRFILES_${N}:T} extract: extract-${CLDRFILES_${N}:T} .endfor + grep 'name="version"' ${UNIDIR}/tools/build.xml | \ + sed 's/.* value="//;s/".*//' > ${UNIDIR}/cldr-version patch:: .if exists(${PATCHDIR}) cd ${UNIDIR} && cat ${PATCHDIR}/patch-* | patch Index: tools/tools/locale/tools/cldr2def.pl =================================================================== --- tools/tools/locale/tools/cldr2def.pl +++ tools/tools/locale/tools/cldr2def.pl @@ -50,6 +50,8 @@ my $ETCDIR = undef; my $TYPE = undef; +my $CLDR_VERSION = undef; + my $result = GetOptions ( "unidir=s" => \$UNIDIR, "etc=s" => \$ETCDIR, @@ -500,6 +502,12 @@ sub transform_collation { + # Read the CLDR version + open(FIN, "$UNIDIR/cldr-version") or die "Cannot open cldr-version"; + read FIN, $CLDR_VERSION, -s FIN; + close(FIN); + $CLDR_VERSION =~ s/\s*$//; + foreach my $l (sort keys(%languages)) { foreach my $f (sort keys(%{$languages{$l}})) { foreach my $c (sort keys(%{$languages{$l}{$f}{data}})) { @@ -861,8 +869,11 @@ my $SRCOUT4 = ""; my $MAPLOC; if ($TYPE eq "colldef") { + # In future, we might want to try to put the CLDR version into + # the .src files with some new syntax, instead of the makefile. $SRCOUT = "localedef \${LOCALEDEF_ENDIAN} -D -U " . "-i \${.IMPSRC} \\\n" . + "\t-V \${CLDR_VERSION} \\\n" . "\t-f \${MAPLOC}/map.\${.TARGET:T:R:E:C/@.*//} " . "\${.OBJDIR}/\${.IMPSRC:T:R}"; $MAPLOC = "MAPLOC=\t\t\${.CURDIR}/../../tools/tools/" . @@ -875,6 +886,7 @@ "\$t.LC_COLLATE: \${.CURDIR}/\$f.src\n" . "\tlocaledef \${LOCALEDEF_ENDIAN} -D -U " . "-i \${.ALLSRC} \\\n" . + "\t-V \${CLDR_VERSION} \\\n" . "\t\t-f \${MAPLOC}/map.\${.TARGET:T:R:E:C/@.*//} \\\n" . "\t\t\${.OBJDIR}/\${.TARGET:T:R}\n" . ".endfor\n\n"; @@ -917,6 +929,13 @@ ${MAPLOC} EOF + if ($TYPE eq "colldef") { + print FOUT < Index: usr.bin/localedef/bootstrap/bootstrap_xlocale_private.h =================================================================== --- usr.bin/localedef/bootstrap/bootstrap_xlocale_private.h +++ usr.bin/localedef/bootstrap/bootstrap_xlocale_private.h @@ -51,4 +51,7 @@ char unused; }; +/* This must agree with the definition in xlocale_private.h. */ +#define XLOCALE_DEF_VERSION_LEN 12 + #endif /* _LOCALDEF_BOOTSTRAP_XLOCALE_PRIVATE_H */ Index: usr.bin/localedef/collate.c =================================================================== --- usr.bin/localedef/collate.c +++ usr.bin/localedef/collate.c @@ -1119,7 +1119,8 @@ collelem_t *ce; collchar_t *cc; subst_t *sb; - char vers[COLLATE_STR_LEN]; + char fmt_version[COLLATE_FMT_VERSION_LEN]; + char def_version[XLOCALE_DEF_VERSION_LEN]; collate_char_t chars[UCHAR_MAX + 1]; collate_large_t *large; collate_subst_t *subst[COLL_WEIGHTS_MAX]; @@ -1160,8 +1161,11 @@ } (void) memset(&chars, 0, sizeof (chars)); - (void) memset(vers, 0, COLLATE_STR_LEN); - (void) strlcpy(vers, COLLATE_VERSION, sizeof (vers)); + (void) memset(fmt_version, 0, COLLATE_FMT_VERSION_LEN); + (void) strlcpy(fmt_version, COLLATE_FMT_VERSION, sizeof (fmt_version)); + (void) memset(def_version, 0, XLOCALE_DEF_VERSION_LEN); + if (version) + (void) strlcpy(def_version, version, sizeof (def_version)); /* * We need to make sure we arrange for the UNDEFINED field @@ -1301,7 +1305,8 @@ collinfo.chain_count = htote(chain_count); collinfo.large_count = htote(large_count); - if ((wr_category(vers, COLLATE_STR_LEN, f) < 0) || + if ((wr_category(fmt_version, COLLATE_FMT_VERSION_LEN, f) < 0) || + (wr_category(def_version, XLOCALE_DEF_VERSION_LEN, f) < 0) || (wr_category(&collinfo, sizeof (collinfo), f) < 0) || (wr_category(&chars, sizeof (chars), f) < 0)) { return; Index: usr.bin/localedef/localedef.h =================================================================== --- usr.bin/localedef/localedef.h +++ usr.bin/localedef/localedef.h @@ -55,6 +55,8 @@ extern int warnok; extern int warnings; +extern char *version; + int yylex(void); void yyerror(const char *); _Noreturn void errf(const char *, ...) __printflike(1, 2); Index: usr.bin/localedef/localedef.1 =================================================================== --- usr.bin/localedef/localedef.1 +++ usr.bin/localedef/localedef.1 @@ -33,7 +33,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 18, 2018 +.Dd November 8, 2020 .Dt LOCALEDEF 1 .Os .Sh NAME @@ -135,6 +135,14 @@ generally not account for East Asian encodings requiring more than a single character cell to display, nor for combining or accent marks that occupy no additional screen width. +.It Fl V Ar version +Specifies a version string describing the version of the locale definition. +This string can be retrieved with +.Xr querylocale 3 , +and is intended to allow applications to detect locale definition changes. +Currently it is stored only for the +.Sy LC_COLLATE +category. .El .Pp The following operands are required: @@ -198,6 +206,7 @@ .Xr locale 1 , .Xr iconv_open 3 , .Xr nl_langinfo 3 , +.Xr querylocale 3 , .Xr strftime 3 , .Xr environ 7 .Sh WARNINGS Index: usr.bin/localedef/localedef.c =================================================================== --- usr.bin/localedef/localedef.c +++ usr.bin/localedef/localedef.c @@ -48,6 +48,7 @@ #include #include #include +#include "collate.h" #include "localedef.h" #include "parser.h" @@ -62,6 +63,7 @@ int warnok = 0; static char *locname = NULL; static char locpath[PATH_MAX]; +char *version = NULL; const char * category_name(void) @@ -253,6 +255,7 @@ (void) fprintf(stderr, " -u encoding : assume encoding\n"); (void) fprintf(stderr, " -w widths : use screen widths file\n"); (void) fprintf(stderr, " -i locsrc : source file for locale\n"); + (void) fprintf(stderr, " -V version : version string for locale\n"); exit(4); } @@ -279,7 +282,7 @@ (void) setlocale(LC_ALL, ""); - while ((c = getopt(argc, argv, "blw:i:cf:u:vUD")) != -1) { + while ((c = getopt(argc, argv, "blw:i:cf:u:vUDV:")) != -1) { switch (c) { case 'D': bsd = 1; @@ -314,6 +317,9 @@ case '?': usage(); break; + case 'V': + version = optarg; + break; } } @@ -325,6 +331,11 @@ (void) printf("Processing locale %s.\n", locname); } + if (version && strlen(version) >= XLOCALE_DEF_VERSION_LEN) { + (void) fprintf(stderr, "Version string too long.\n"); + exit(1); + } + if (cfname) { if (verbose) (void) printf("Loading charmap %s.\n", cfname);