50f89d
#define _GNU_SOURCE
50f89d
#include <assert.h>
50f89d
#include <dirent.h>
50f89d
#include <errno.h>
50f89d
#include <fcntl.h>
50f89d
#include <locale.h>
50f89d
#include <stdarg.h>
50f89d
#include <stdbool.h>
50f89d
#include <stdio.h>
50f89d
#include <stdlib.h>
50f89d
#include <getopt.h>
50f89d
#include <string.h>
50f89d
#include <sys/mman.h>
50f89d
#include <sys/stat.h>
50f89d
#include <unistd.h>
50f89d
#include "../locale/hashval.h"
50f89d
#define __LC_LAST 13
50f89d
#include "../locale/locarchive.h"
50f89d
#include "../crypt/md5.h"
50f89d
50f89d
const char *alias_file = DATADIR "/locale/locale.alias";
50f89d
const char *locar_file = PREFIX "/lib/locale/locale-archive";
50f89d
const char *tmpl_file = PREFIX "/lib/locale/locale-archive.tmpl";
50f89d
const char *loc_path = PREFIX "/lib/locale/";
50f89d
/* Flags set by `--verbose` option.  */
50f89d
int be_quiet = 1;
50f89d
int verbose = 0;
50f89d
int max_locarchive_open_retry = 10;
50f89d
const char *output_prefix;
50f89d
50f89d
/* Endianness should have been taken care of by localedef.  We don't need to do
50f89d
   additional swapping.  We need this variable exported however, since
50f89d
   locarchive.c uses it to determine if it needs to swap endianness of a value
50f89d
   before writing to or reading from the archive.  */
50f89d
bool swap_endianness_p = false;
50f89d
50f89d
static const char *locnames[] =
50f89d
  {
50f89d
#define DEFINE_CATEGORY(category, category_name, items, a) \
50f89d
  [category] = category_name,
50f89d
#include "../locale/categories.def"
50f89d
#undef  DEFINE_CATEGORY
50f89d
  };
50f89d
50f89d
static int
50f89d
is_prime (unsigned long candidate)
50f89d
{
50f89d
  /* No even number and none less than 10 will be passed here.  */
50f89d
  unsigned long int divn = 3;
50f89d
  unsigned long int sq = divn * divn;
50f89d
50f89d
  while (sq < candidate && candidate % divn != 0)
50f89d
    {
50f89d
      ++divn;
50f89d
      sq += 4 * divn;
50f89d
      ++divn;
50f89d
    }
50f89d
50f89d
  return candidate % divn != 0;
50f89d
}
50f89d
50f89d
unsigned long
50f89d
next_prime (unsigned long seed)
50f89d
{
50f89d
  /* Make it definitely odd.  */
50f89d
  seed |= 1;
50f89d
50f89d
  while (!is_prime (seed))
50f89d
    seed += 2;
50f89d
50f89d
  return seed;
50f89d
}
50f89d
50f89d
void
50f89d
error (int status, int errnum, const char *message, ...)
50f89d
{
50f89d
  va_list args;
50f89d
50f89d
  va_start (args, message);
50f89d
  fflush (stdout);
50f89d
  fprintf (stderr, "%s: ", program_invocation_name);
50f89d
  vfprintf (stderr, message, args);
50f89d
  va_end (args);
50f89d
  if (errnum)
50f89d
    fprintf (stderr, ": %s", strerror (errnum));
50f89d
  putc ('\n', stderr);
50f89d
  fflush (stderr);
50f89d
  if (status)
50f89d
    exit (errnum == EROFS ? 0 : status);
50f89d
}
50f89d
50f89d
void *
50f89d
xmalloc (size_t size)
50f89d
{
50f89d
  void *p = malloc (size);
50f89d
  if (p == NULL)
50f89d
    error (EXIT_FAILURE, errno, "could not allocate %zd bytes of memory", size);
50f89d
  return p;
50f89d
}
50f89d
50f89d
static void
50f89d
open_tmpl_archive (struct locarhandle *ah)
50f89d
{
50f89d
  struct stat64 st;
50f89d
  int fd;
50f89d
  struct locarhead head;
50f89d
  const char *archivefname = ah->fname == NULL ? tmpl_file : ah->fname;
50f89d
50f89d
  /* Open the archive.  We must have exclusive write access.  */
50f89d
  fd = open64 (archivefname, O_RDONLY);
50f89d
  if (fd == -1)
50f89d
    error (EXIT_FAILURE, errno, "cannot open locale archive template file \"%s\"",
50f89d
	   archivefname);
50f89d
50f89d
  if (fstat64 (fd, &st) < 0)
50f89d
    error (EXIT_FAILURE, errno, "cannot stat locale archive template file \"%s\"",
50f89d
	   archivefname);
50f89d
50f89d
  /* Read the header.  */
50f89d
  if (TEMP_FAILURE_RETRY (read (fd, &head, sizeof (head))) != sizeof (head))
50f89d
    error (EXIT_FAILURE, errno, "cannot read archive header");
50f89d
50f89d
  ah->fd = fd;
50f89d
  ah->mmaped = (head.sumhash_offset
50f89d
		+ head.sumhash_size * sizeof (struct sumhashent));
50f89d
  if (ah->mmaped > (unsigned long) st.st_size)
50f89d
    error (EXIT_FAILURE, 0, "locale archive template file truncated");
50f89d
  ah->mmaped = st.st_size;
50f89d
  ah->reserved = st.st_size;
50f89d
50f89d
  /* Now we know how large the administrative information part is.
50f89d
     Map all of it.  */
50f89d
  ah->addr = mmap64 (NULL, ah->mmaped, PROT_READ, MAP_SHARED, fd, 0);
50f89d
  if (ah->addr == MAP_FAILED)
50f89d
    error (EXIT_FAILURE, errno, "cannot map archive header");
50f89d
}
50f89d
50f89d
/* Open the locale archive.  */
50f89d
extern void open_archive (struct locarhandle *ah, bool readonly);
50f89d
50f89d
/* Close the locale archive.  */
50f89d
extern void close_archive (struct locarhandle *ah);
50f89d
50f89d
/* Add given locale data to the archive.  */
50f89d
extern int add_locale_to_archive (struct locarhandle *ah, const char *name,
50f89d
				  locale_data_t data, bool replace);
50f89d
50f89d
extern void add_alias (struct locarhandle *ah, const char *alias,
50f89d
		       bool replace, const char *oldname,
50f89d
		       uint32_t *locrec_offset_p);
50f89d
50f89d
extern struct namehashent *
50f89d
insert_name (struct locarhandle *ah,
50f89d
	     const char *name, size_t name_len, bool replace);
50f89d
50f89d
struct nameent
50f89d
{
50f89d
  char *name;
50f89d
  struct locrecent *locrec;
50f89d
};
50f89d
50f89d
struct dataent
50f89d
{
50f89d
  const unsigned char *sum;
50f89d
  uint32_t file_offset;
50f89d
};
50f89d
50f89d
static int
50f89d
nameentcmp (const void *a, const void *b)
50f89d
{
50f89d
  struct locrecent *la = ((const struct nameent *) a)->locrec;
50f89d
  struct locrecent *lb = ((const struct nameent *) b)->locrec;
50f89d
  uint32_t start_a = -1, end_a = 0;
50f89d
  uint32_t start_b = -1, end_b = 0;
50f89d
  int cnt;
50f89d
50f89d
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
    if (cnt != LC_ALL)
50f89d
      {
50f89d
	if (la->record[cnt].offset < start_a)
50f89d
	  start_a = la->record[cnt].offset;
50f89d
	if (la->record[cnt].offset + la->record[cnt].len > end_a)
50f89d
	  end_a = la->record[cnt].offset + la->record[cnt].len;
50f89d
      }
50f89d
  assert (start_a != (uint32_t)-1);
50f89d
  assert (end_a != 0);
50f89d
50f89d
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
    if (cnt != LC_ALL)
50f89d
      {
50f89d
	if (lb->record[cnt].offset < start_b)
50f89d
	  start_b = lb->record[cnt].offset;
50f89d
	if (lb->record[cnt].offset + lb->record[cnt].len > end_b)
50f89d
	  end_b = lb->record[cnt].offset + lb->record[cnt].len;
50f89d
      }
50f89d
  assert (start_b != (uint32_t)-1);
50f89d
  assert (end_b != 0);
50f89d
50f89d
  if (start_a != start_b)
50f89d
    return (int)start_a - (int)start_b;
50f89d
  return (int)end_a - (int)end_b;
50f89d
}
50f89d
50f89d
static int
50f89d
dataentcmp (const void *a, const void *b)
50f89d
{
50f89d
  if (((const struct dataent *) a)->file_offset
50f89d
      < ((const struct dataent *) b)->file_offset)
50f89d
    return -1;
50f89d
50f89d
  if (((const struct dataent *) a)->file_offset
50f89d
      > ((const struct dataent *) b)->file_offset)
50f89d
    return 1;
50f89d
50f89d
  return 0;
50f89d
}
50f89d
50f89d
static int
50f89d
sumsearchfn (const void *key, const void *ent)
50f89d
{
50f89d
  uint32_t keyn = *(uint32_t *)key;
50f89d
  uint32_t entn = ((struct dataent *)ent)->file_offset;
50f89d
50f89d
  if (keyn < entn)
50f89d
    return -1;
50f89d
  if (keyn > entn)
50f89d
    return 1;
50f89d
  return 0;
50f89d
}
50f89d
50f89d
static void
50f89d
compute_data (struct locarhandle *ah, struct nameent *name, size_t sumused,
50f89d
	      struct dataent *files, locale_data_t data)
50f89d
{
50f89d
  int cnt;
50f89d
  struct locrecent *locrec = name->locrec;
50f89d
  struct dataent *file;
50f89d
  data[LC_ALL].addr = ((char *) ah->addr) + locrec->record[LC_ALL].offset;
50f89d
  data[LC_ALL].size = locrec->record[LC_ALL].len;
50f89d
  for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
    if (cnt != LC_ALL)
50f89d
      {
50f89d
	data[cnt].addr = ((char *) ah->addr) + locrec->record[cnt].offset;
50f89d
	data[cnt].size = locrec->record[cnt].len;
50f89d
	if (data[cnt].addr >= data[LC_ALL].addr
50f89d
	    && data[cnt].addr + data[cnt].size
50f89d
	       <= data[LC_ALL].addr + data[LC_ALL].size)
50f89d
	  __md5_buffer (data[cnt].addr, data[cnt].size, data[cnt].sum);
50f89d
	else
50f89d
	  {
50f89d
	    file = bsearch (&locrec->record[cnt].offset, files, sumused,
50f89d
			    sizeof (*files), sumsearchfn);
50f89d
	    if (file == NULL)
50f89d
	      error (EXIT_FAILURE, 0, "inconsistent template file");
50f89d
	    memcpy (data[cnt].sum, file->sum, sizeof (data[cnt].sum));
50f89d
	  }
50f89d
      }
50f89d
}
50f89d
50f89d
static int
50f89d
fill_archive (struct locarhandle *tmpl_ah,
50f89d
	      const char *fname,
50f89d
	      size_t install_langs_count, char *install_langs_list[],
50f89d
	      size_t nlist, char *list[],
50f89d
	      const char *primary)
50f89d
{
50f89d
  struct locarhandle ah;
50f89d
  struct locarhead *head;
50f89d
  int result = 0;
50f89d
  struct nameent *names;
50f89d
  struct namehashent *namehashtab;
50f89d
  size_t cnt, used;
50f89d
  struct dataent *files;
50f89d
  struct sumhashent *sumhashtab;
50f89d
  size_t sumused;
50f89d
  struct locrecent *primary_locrec = NULL;
50f89d
  struct nameent *primary_nameent = NULL;
50f89d
50f89d
  head = tmpl_ah->addr;
50f89d
  names = (struct nameent *) malloc (head->namehash_used
50f89d
				     * sizeof (struct nameent));
50f89d
  files = (struct dataent *) malloc (head->sumhash_used
50f89d
				     * sizeof (struct dataent));
50f89d
  if (names == NULL || files == NULL)
50f89d
    error (EXIT_FAILURE, errno, "could not allocate tables");
50f89d
50f89d
  namehashtab = (struct namehashent *) ((char *) tmpl_ah->addr
50f89d
					+ head->namehash_offset);
50f89d
  sumhashtab = (struct sumhashent *) ((char *) tmpl_ah->addr
50f89d
				      + head->sumhash_offset);
50f89d
50f89d
  for (cnt = used = 0; cnt < head->namehash_size; ++cnt)
50f89d
    if (namehashtab[cnt].locrec_offset != 0)
50f89d
      {
50f89d
	char * name;
50f89d
	int i;
50f89d
	assert (used < head->namehash_used);
50f89d
        name = tmpl_ah->addr + namehashtab[cnt].name_offset;
50f89d
        if (install_langs_count == 0)
50f89d
          {
50f89d
	    /* Always intstall the entry.  */
50f89d
            names[used].name = name;
50f89d
            names[used++].locrec
50f89d
                = (struct locrecent *) ((char *) tmpl_ah->addr +
50f89d
                                        namehashtab[cnt].locrec_offset);
50f89d
          }
50f89d
        else
50f89d
          {
50f89d
	    /* Only install the entry if the user asked for it via
50f89d
	       --install-langs.  */
50f89d
            for (i = 0; i < install_langs_count; i++)
50f89d
              {
50f89d
		/* Add one for "_" and one for the null terminator.  */
50f89d
		size_t len = strlen (install_langs_list[i]) + 2;
50f89d
		char *install_lang = (char *)xmalloc (len);
50f89d
                strcpy (install_lang, install_langs_list[i]);
50f89d
                if (strchr (install_lang, '_') == NULL)
50f89d
                  strcat (install_lang, "_");
50f89d
                if (strncmp (name, install_lang, strlen (install_lang)) == 0)
50f89d
                  {
50f89d
                    names[used].name = name;
50f89d
                    names[used++].locrec
50f89d
		      = (struct locrecent *) ((char *)tmpl_ah->addr
50f89d
					      + namehashtab[cnt].locrec_offset);
50f89d
                  }
50f89d
		free (install_lang);
50f89d
              }
50f89d
          }
50f89d
      }
50f89d
50f89d
  /* Sort the names.  */
50f89d
  qsort (names, used, sizeof (struct nameent), nameentcmp);
50f89d
50f89d
  for (cnt = sumused = 0; cnt < head->sumhash_size; ++cnt)
50f89d
    if (sumhashtab[cnt].file_offset != 0)
50f89d
      {
50f89d
	assert (sumused < head->sumhash_used);
50f89d
	files[sumused].sum = (const unsigned char *) sumhashtab[cnt].sum;
50f89d
	files[sumused++].file_offset = sumhashtab[cnt].file_offset;
50f89d
      }
50f89d
50f89d
  /* Sort by file locations.  */
50f89d
  qsort (files, sumused, sizeof (struct dataent), dataentcmp);
50f89d
50f89d
  /* Open the archive.  This call never returns if we cannot
50f89d
     successfully open the archive.  */
50f89d
  ah.fname = NULL;
50f89d
  if (fname != NULL)
50f89d
    ah.fname = fname;
50f89d
  open_archive (&ah, false);
50f89d
50f89d
  if (primary != NULL)
50f89d
    {
50f89d
      for (cnt = 0; cnt < used; ++cnt)
50f89d
	if (strcmp (names[cnt].name, primary) == 0)
50f89d
	  break;
50f89d
      if (cnt < used)
50f89d
	{
50f89d
	  locale_data_t data;
50f89d
50f89d
	  compute_data (tmpl_ah, &names[cnt], sumused, files, data);
50f89d
	  result |= add_locale_to_archive (&ah, primary, data, 0);
50f89d
	  primary_locrec = names[cnt].locrec;
50f89d
	  primary_nameent = &names[cnt];
50f89d
	}
50f89d
    }
50f89d
50f89d
  for (cnt = 0; cnt < used; ++cnt)
50f89d
    if (&names[cnt] == primary_nameent)
50f89d
      continue;
50f89d
    else if ((cnt > 0 && names[cnt - 1].locrec == names[cnt].locrec)
50f89d
	     || names[cnt].locrec == primary_locrec)
50f89d
      {
50f89d
	const char *oldname;
50f89d
	struct namehashent *namehashent;
50f89d
	uint32_t locrec_offset;
50f89d
50f89d
	if (names[cnt].locrec == primary_locrec)
50f89d
	  oldname = primary;
50f89d
	else
50f89d
	  oldname = names[cnt - 1].name;
50f89d
	namehashent = insert_name (&ah, oldname, strlen (oldname), true);
50f89d
	assert (namehashent->name_offset != 0);
50f89d
	assert (namehashent->locrec_offset != 0);
50f89d
	locrec_offset = namehashent->locrec_offset;
50f89d
	add_alias (&ah, names[cnt].name, 0, oldname, &locrec_offset);
50f89d
      }
50f89d
    else
50f89d
      {
50f89d
	locale_data_t data;
50f89d
50f89d
	compute_data (tmpl_ah, &names[cnt], sumused, files, data);
50f89d
	result |= add_locale_to_archive (&ah, names[cnt].name, data, 0);
50f89d
      }
50f89d
50f89d
  while (nlist-- > 0)
50f89d
    {
50f89d
      const char *fname = *list++;
50f89d
      size_t fnamelen = strlen (fname);
50f89d
      struct stat64 st;
50f89d
      DIR *dirp;
50f89d
      struct dirent64 *d;
50f89d
      int seen;
50f89d
      locale_data_t data;
50f89d
      int cnt;
50f89d
50f89d
      /* First see whether this really is a directory and whether it
50f89d
	 contains all the require locale category files.  */
50f89d
      if (stat64 (fname, &st) < 0)
50f89d
	{
50f89d
	  error (0, 0, "stat of \"%s\" failed: %s: ignored", fname,
50f89d
		 strerror (errno));
50f89d
	  continue;
50f89d
	}
50f89d
      if (!S_ISDIR (st.st_mode))
50f89d
	{
50f89d
	  error (0, 0, "\"%s\" is no directory; ignored", fname);
50f89d
	  continue;
50f89d
	}
50f89d
50f89d
      dirp = opendir (fname);
50f89d
      if (dirp == NULL)
50f89d
	{
50f89d
	  error (0, 0, "cannot open directory \"%s\": %s: ignored",
50f89d
		 fname, strerror (errno));
50f89d
	  continue;
50f89d
	}
50f89d
50f89d
      seen = 0;
50f89d
      while ((d = readdir64 (dirp)) != NULL)
50f89d
	{
50f89d
	  for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
	    if (cnt != LC_ALL)
50f89d
	      if (strcmp (d->d_name, locnames[cnt]) == 0)
50f89d
		{
50f89d
		  unsigned char d_type;
50f89d
50f89d
		  /* We have an object of the required name.  If it's
50f89d
		     a directory we have to look at a file with the
50f89d
		     prefix "SYS_".  Otherwise we have found what we
50f89d
		     are looking for.  */
50f89d
#ifdef _DIRENT_HAVE_D_TYPE
50f89d
		  d_type = d->d_type;
50f89d
50f89d
		  if (d_type != DT_REG)
50f89d
#endif
50f89d
		    {
50f89d
		      char fullname[fnamelen + 2 * strlen (d->d_name) + 7];
50f89d
50f89d
#ifdef _DIRENT_HAVE_D_TYPE
c5d972
		      if (d_type == DT_UNKNOWN || d_type == DT_LNK)
50f89d
#endif
50f89d
			{
50f89d
			  strcpy (stpcpy (stpcpy (fullname, fname), "/"),
50f89d
				  d->d_name);
50f89d
50f89d
			  if (stat64 (fullname, &st) == -1)
50f89d
			    /* We cannot stat the file, ignore it.  */
50f89d
			    break;
50f89d
50f89d
			  d_type = IFTODT (st.st_mode);
50f89d
			}
50f89d
50f89d
		      if (d_type == DT_DIR)
50f89d
			{
50f89d
			  /* We have to do more tests.  The file is a
50f89d
			     directory and it therefore must contain a
50f89d
			     regular file with the same name except a
50f89d
			     "SYS_" prefix.  */
50f89d
			  char *t = stpcpy (stpcpy (fullname, fname), "/");
50f89d
			  strcpy (stpcpy (stpcpy (t, d->d_name), "/SYS_"),
50f89d
				  d->d_name);
50f89d
50f89d
			  if (stat64 (fullname, &st) == -1)
50f89d
			    /* There is no SYS_* file or we cannot
50f89d
			       access it.  */
50f89d
			    break;
50f89d
50f89d
			  d_type = IFTODT (st.st_mode);
50f89d
			}
50f89d
		    }
50f89d
50f89d
		  /* If we found a regular file (eventually after
50f89d
		     following a symlink) we are successful.  */
50f89d
		  if (d_type == DT_REG)
50f89d
		    ++seen;
50f89d
		  break;
50f89d
		}
50f89d
	}
50f89d
50f89d
      closedir (dirp);
50f89d
50f89d
      if (seen != __LC_LAST - 1)
50f89d
	{
50f89d
	  /* We don't have all locale category files.  Ignore the name.  */
50f89d
	  error (0, 0, "incomplete set of locale files in \"%s\"",
50f89d
		 fname);
50f89d
	  continue;
50f89d
	}
50f89d
50f89d
      /* Add the files to the archive.  To do this we first compute
50f89d
	 sizes and the MD5 sums of all the files.  */
50f89d
      for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
	if (cnt != LC_ALL)
50f89d
	  {
50f89d
	    char fullname[fnamelen + 2 * strlen (locnames[cnt]) + 7];
50f89d
	    int fd;
50f89d
50f89d
	    strcpy (stpcpy (stpcpy (fullname, fname), "/"), locnames[cnt]);
50f89d
	    fd = open64 (fullname, O_RDONLY);
50f89d
	    if (fd == -1 || fstat64 (fd, &st) == -1)
50f89d
	      {
50f89d
		/* Cannot read the file.  */
50f89d
		if (fd != -1)
50f89d
		  close (fd);
50f89d
		break;
50f89d
	      }
50f89d
50f89d
	    if (S_ISDIR (st.st_mode))
50f89d
	      {
50f89d
		char *t;
50f89d
		close (fd);
50f89d
		t = stpcpy (stpcpy (fullname, fname), "/");
50f89d
		strcpy (stpcpy (stpcpy (t, locnames[cnt]), "/SYS_"),
50f89d
			locnames[cnt]);
50f89d
50f89d
		fd = open64 (fullname, O_RDONLY);
50f89d
		if (fd == -1 || fstat64 (fd, &st) == -1
50f89d
		    || !S_ISREG (st.st_mode))
50f89d
		  {
50f89d
		    if (fd != -1)
50f89d
		      close (fd);
50f89d
		    break;
50f89d
		  }
50f89d
	      }
50f89d
50f89d
	    /* Map the file.  */
50f89d
	    data[cnt].addr = mmap64 (NULL, st.st_size, PROT_READ, MAP_SHARED,
50f89d
				     fd, 0);
50f89d
	    if (data[cnt].addr == MAP_FAILED)
50f89d
	      {
50f89d
		/* Cannot map it.  */
50f89d
		close (fd);
50f89d
		break;
50f89d
	      }
50f89d
50f89d
	    data[cnt].size = st.st_size;
50f89d
	    __md5_buffer (data[cnt].addr, st.st_size, data[cnt].sum);
50f89d
50f89d
	    /* We don't need the file descriptor anymore.  */
50f89d
	    close (fd);
50f89d
	  }
50f89d
50f89d
      if (cnt != __LC_LAST)
50f89d
	{
50f89d
	  while (cnt-- > 0)
50f89d
	    if (cnt != LC_ALL)
50f89d
	      munmap (data[cnt].addr, data[cnt].size);
50f89d
50f89d
	  error (0, 0, "cannot read all files in \"%s\": ignored", fname);
50f89d
50f89d
	  continue;
50f89d
	}
50f89d
50f89d
      result |= add_locale_to_archive (&ah, basename (fname), data, 0);
50f89d
50f89d
      for (cnt = 0; cnt < __LC_LAST; ++cnt)
50f89d
	if (cnt != LC_ALL)
50f89d
	  munmap (data[cnt].addr, data[cnt].size);
50f89d
    }
50f89d
50f89d
  /* We are done.  */
50f89d
  close_archive (&ah;;
50f89d
50f89d
  return result;
50f89d
}
50f89d
50f89d
void usage()
50f89d
{
50f89d
  printf ("\
50f89d
Usage: build-locale-archive [OPTION]... [TEMPLATE-FILE] [ARCHIVE-FILE]\n\
50f89d
 Builds a locale archive from a template file.\n\
50f89d
 Options:\n\
50f89d
  -h, --help                 Print this usage message.\n\
50f89d
  -v, --verbose              Verbose execution.\n\
50f89d
  -l, --install-langs=LIST   Only include locales given in LIST into the \n\
50f89d
                             locale archive.  LIST is a colon separated list\n\
50f89d
                             of locale prefixes, for example \"de:en:ja\".\n\
50f89d
                             The special argument \"all\" means to install\n\
50f89d
                             all languages and it must be present by itself.\n\
50f89d
                             If \"all\" is present with any other language it\n\
50f89d
                             will be treated as the name of a locale.\n\
50f89d
                             If the --install-langs option is missing, all\n\
50f89d
                             locales are installed. The colon separated list\n\
50f89d
                             can contain any strings matching the beginning of\n\
50f89d
                             locale names.\n\
50f89d
                             If a string does not contain a \"_\", it is added.\n\
50f89d
                             Examples:\n\
50f89d
                               --install-langs=\"en\"\n\
50f89d
                                 installs en_US, en_US.iso88591,\n\
50f89d
                                 en_US.iso885915, en_US.utf8,\n\
50f89d
                                 en_GB ...\n\
50f89d
                               --install-langs=\"en_US.utf8\"\n\
50f89d
                                 installs only en_US.utf8.\n\
50f89d
                               --install-langs=\"ko\"\n\
50f89d
                                 installs ko_KR, ko_KR.euckr,\n\
50f89d
                                 ko_KR.utf8 but *not* kok_IN\n\
50f89d
                                 because \"ko\" does not contain\n\
50f89d
                                 \"_\" and it is silently added\n\
50f89d
                               --install-langs\"ko:kok\"\n\
50f89d
                                 installs ko_KR, ko_KR.euckr,\n\
50f89d
                                 ko_KR.utf8, kok_IN, and\n\
50f89d
                                 kok_IN.utf8.\n\
50f89d
                               --install-langs=\"POSIX\" will\n\
50f89d
                                 installs *no* locales at all\n\
50f89d
                                 because POSIX matches none of\n\
50f89d
                                 the locales. Actually, any string\n\
50f89d
                                 matching nothing will do that.\n\
50f89d
                                 POSIX and C will always be\n\
50f89d
                                 available because they are\n\
50f89d
                                 builtin.\n\
50f89d
                             Aliases are installed as well,\n\
50f89d
                             i.e. --install-langs=\"de\"\n\
50f89d
                             will install not only every locale starting with\n\
50f89d
                             \"de\" but also the aliases \"deutsch\"\n\
50f89d
                             and and \"german\" although the latter does not\n\
50f89d
                             start with \"de\".\n\
50f89d
\n\
50f89d
  If the arguments TEMPLATE-FILE and ARCHIVE-FILE are not given the locations\n\
50f89d
  where the glibc used expects these files are used by default.\n\
50f89d
");
50f89d
}
50f89d
50f89d
int main (int argc, char *argv[])
50f89d
{
50f89d
  char path[4096];
50f89d
  DIR *dirp;
50f89d
  struct dirent64 *d;
50f89d
  struct stat64 st;
50f89d
  char *list[16384], *primary;
50f89d
  char *lang;
50f89d
  int install_langs_count = 0;
50f89d
  int i;
50f89d
  char *install_langs_arg, *ila_start;
50f89d
  char **install_langs_list = NULL;
50f89d
  unsigned int cnt = 0;
50f89d
  struct locarhandle tmpl_ah;
50f89d
  char *new_locar_fname = NULL;
50f89d
  size_t loc_path_len = strlen (loc_path);
50f89d
50f89d
  while (1)
50f89d
    {
50f89d
      int c;
50f89d
50f89d
      static struct option long_options[] =
50f89d
        {
50f89d
            {"help",            no_argument,       0, 'h'},
50f89d
            {"verbose",         no_argument,       0, 'v'},
50f89d
            {"install-langs",   required_argument, 0, 'l'},
50f89d
            {0, 0, 0, 0}
50f89d
        };
50f89d
      /* getopt_long stores the option index here. */
50f89d
      int option_index = 0;
50f89d
50f89d
      c = getopt_long (argc, argv, "vhl:",
50f89d
                       long_options, &option_index);
50f89d
50f89d
      /* Detect the end of the options. */
50f89d
      if (c == -1)
50f89d
        break;
50f89d
50f89d
      switch (c)
50f89d
        {
50f89d
        case 0:
50f89d
          printf ("unknown option %s", long_options[option_index].name);
50f89d
          if (optarg)
50f89d
            printf (" with arg %s", optarg);
50f89d
          printf ("\n");
50f89d
          usage ();
50f89d
          exit (1);
50f89d
50f89d
        case 'v':
50f89d
          verbose = 1;
50f89d
          be_quiet = 0;
50f89d
          break;
50f89d
50f89d
        case 'h':
50f89d
          usage ();
50f89d
          exit (0);
50f89d
50f89d
        case 'l':
50f89d
          install_langs_arg = ila_start = strdup (optarg);
50f89d
          /* If the argument to --install-lang is "all", do
50f89d
             not limit the list of languages to install and install
50f89d
             them all.  We do not support installing a single locale
50f89d
	     called "all".  */
50f89d
#define MAGIC_INSTALL_ALL "all"
50f89d
          if (install_langs_arg != NULL
50f89d
	      && install_langs_arg[0] != '\0'
50f89d
	      && !(strncmp(install_langs_arg, MAGIC_INSTALL_ALL,
50f89d
			   strlen(MAGIC_INSTALL_ALL)) == 0
50f89d
		   && strlen (install_langs_arg) == 3))
50f89d
            {
50f89d
	      /* Count the number of languages we will install.  */
50f89d
              while (true)
50f89d
                {
50f89d
                  lang = strtok(install_langs_arg, ":;,");
50f89d
                  if (lang == NULL)
50f89d
                    break;
50f89d
                  install_langs_count++;
50f89d
                  install_langs_arg = NULL;
50f89d
                }
50f89d
	      free (ila_start);
50f89d
50f89d
	      /* Reject an entire string made up of delimiters.  */
50f89d
	      if (install_langs_count == 0)
50f89d
		break;
50f89d
50f89d
	      /* Copy the list.  */
50f89d
	      install_langs_list = (char **)xmalloc (sizeof(char *) * install_langs_count);
50f89d
	      install_langs_arg = ila_start = strdup (optarg);
50f89d
	      install_langs_count = 0;
50f89d
	      while (true)
50f89d
                {
50f89d
                  lang = strtok(install_langs_arg, ":;,");
50f89d
                  if (lang == NULL)
50f89d
                    break;
50f89d
                  install_langs_list[install_langs_count] = lang;
50f89d
		  install_langs_count++;
50f89d
                  install_langs_arg = NULL;
50f89d
                }
50f89d
            }
50f89d
          break;
50f89d
50f89d
        case '?':
50f89d
          /* getopt_long already printed an error message. */
50f89d
          usage ();
50f89d
          exit (0);
50f89d
50f89d
        default:
50f89d
          abort ();
50f89d
        }
50f89d
    }
50f89d
  tmpl_ah.fname = NULL;
50f89d
  if (optind < argc)
50f89d
    tmpl_ah.fname = argv[optind];
50f89d
  if (optind + 1 < argc)
50f89d
    new_locar_fname = argv[optind + 1];
50f89d
  if (verbose)
50f89d
    {
50f89d
      if (tmpl_ah.fname)
50f89d
        printf("input archive file specified on command line: %s\n",
50f89d
               tmpl_ah.fname);
50f89d
      else
50f89d
        printf("using default input archive file.\n");
50f89d
      if (new_locar_fname)
50f89d
        printf("output archive file specified on command line: %s\n",
50f89d
               new_locar_fname);
50f89d
      else
50f89d
        printf("using default output archive file.\n");
50f89d
    }
50f89d
50f89d
  dirp = opendir (loc_path);
50f89d
  if (dirp == NULL)
50f89d
    error (EXIT_FAILURE, errno, "cannot open directory \"%s\"", loc_path);
50f89d
50f89d
  open_tmpl_archive (&tmpl_ah);
50f89d
50f89d
  if (new_locar_fname)
50f89d
    unlink (new_locar_fname);
50f89d
  else
50f89d
    unlink (locar_file);
50f89d
  primary = getenv ("LC_ALL");
50f89d
  if (primary == NULL)
50f89d
    primary = getenv ("LANG");
50f89d
  if (primary != NULL)
50f89d
    {
50f89d
      if (strncmp (primary, "ja", 2) != 0
50f89d
	  && strncmp (primary, "ko", 2) != 0
50f89d
	  && strncmp (primary, "zh", 2) != 0)
50f89d
	{
50f89d
	  char *ptr = malloc (strlen (primary) + strlen (".utf8") + 1), *p, *q;
50f89d
	  /* This leads to invalid locales sometimes:
50f89d
	     de_DE.iso885915@euro -> de_DE.utf8@euro */
50f89d
	  if (ptr != NULL)
50f89d
	    {
50f89d
	      p = ptr;
50f89d
	      q = primary;
50f89d
	      while (*q && *q != '.' && *q != '@')
50f89d
		*p++ = *q++;
50f89d
	      if (*q == '.')
50f89d
		while (*q && *q != '@')
50f89d
		  q++;
50f89d
	      p = stpcpy (p, ".utf8");
50f89d
	      strcpy (p, q);
50f89d
	      primary = ptr;
50f89d
	    }
50f89d
	  else
50f89d
	    primary = NULL;
50f89d
	}
50f89d
    }
50f89d
50f89d
  memcpy (path, loc_path, loc_path_len);
50f89d
50f89d
  while ((d = readdir64 (dirp)) != NULL)
50f89d
    {
50f89d
      if (strcmp (d->d_name, ".") == 0 || strcmp (d->d_name, "..") == 0)
50f89d
	continue;
50f89d
      if (strchr (d->d_name, '_') == NULL)
50f89d
	continue;
50f89d
50f89d
      size_t d_name_len = strlen (d->d_name);
50f89d
      if (loc_path_len + d_name_len + 1 > sizeof (path))
50f89d
	{
50f89d
	  error (0, 0, "too long filename \"%s\"", d->d_name);
50f89d
	  continue;
50f89d
	}
50f89d
50f89d
      memcpy (path + loc_path_len, d->d_name, d_name_len + 1);
50f89d
      if (stat64 (path, &st) < 0)
50f89d
	{
50f89d
	  error (0, errno, "cannot stat \"%s\"", path);
50f89d
	  continue;
50f89d
	}
50f89d
      if (! S_ISDIR (st.st_mode))
50f89d
	continue;
50f89d
      if (cnt == 16384)
50f89d
	{
50f89d
	  error (0, 0, "too many directories in \"%s\"", loc_path);
50f89d
	  break;
50f89d
	}
50f89d
      list[cnt] = strdup (path);
50f89d
      if (list[cnt] == NULL)
50f89d
	{
50f89d
	  error (0, errno, "cannot add file to list \"%s\"", path);
50f89d
	  continue;
50f89d
	}
50f89d
      if (primary != NULL && cnt > 0 && strcmp (primary, d->d_name) == 0)
50f89d
	{
50f89d
	  char *p = list[0];
50f89d
	  list[0] = list[cnt];
50f89d
	  list[cnt] = p;
50f89d
	}
50f89d
      cnt++;
50f89d
    }
50f89d
  closedir (dirp);
50f89d
  /* Store the archive to the file specified as the second argument on the
50f89d
     command line or the default locale archive.  */
50f89d
  fill_archive (&tmpl_ah, new_locar_fname,
50f89d
                install_langs_count, install_langs_list,
50f89d
                cnt, list, primary);
50f89d
  close_archive (&tmpl_ah);
50f89d
  truncate (tmpl_file, 0);
50f89d
  if (install_langs_count > 0)
50f89d
    {
50f89d
      free (ila_start);
50f89d
      free (install_langs_list);
50f89d
    }
50f89d
  char *tz_argv[] = { "/usr/sbin/tzdata-update", NULL };
50f89d
  execve (tz_argv[0], (char *const *)tz_argv, (char *const *)&tz_argv[1]);
50f89d
  exit (0);
50f89d
}