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