lcFile.c revision 57f47464
1/*
2 *
3 * Copyright IBM Corporation 1993
4 *
5 * All Rights Reserved
6 *
7 * License to use, copy, modify, and distribute this software and its
8 * documentation for any purpose and without fee is hereby granted,
9 * provided that the above copyright notice appear in all copies and that
10 * both that copyright notice and this permission notice appear in
11 * supporting documentation, and that the name of IBM not be
12 * used in advertising or publicity pertaining to distribution of the
13 * software without specific, written prior permission.
14 *
15 * IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
16 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS, AND
17 * NONINFRINGEMENT OF THIRD PARTY RIGHTS, IN NO EVENT SHALL
18 * IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
19 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
20 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
21 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
22 * SOFTWARE.
23 *
24*/
25
26#ifdef HAVE_CONFIG_H
27#include <config.h>
28#endif
29#include <stdlib.h>
30#include <stdio.h>
31#include <ctype.h>
32#include "Xlibint.h"
33#include "XlcPubI.h"
34#include <X11/Xos.h>
35#include <unistd.h>
36
37/************************************************************************/
38
39#ifdef __UNIXOS2__
40# define seteuid setuid
41#endif
42#define	iscomment(ch)	((ch) == '#' || (ch) == '\0')
43#if defined(WIN32)
44#define isreadable(f)	(_XAccessFile(f))
45#else
46#define isreadable(f)	((access((f), R_OK) != -1) ? 1 : 0)
47#endif
48
49#ifndef __UNIXOS2__
50#define LC_PATHDELIM ':'
51#else
52#define LC_PATHDELIM ';'
53#endif
54
55#define XLC_BUFSIZE 256
56
57#ifndef X_NOT_POSIX
58#ifdef _POSIX_SOURCE
59#include <limits.h>
60#else
61#define _POSIX_SOURCE
62#include <limits.h>
63#undef _POSIX_SOURCE
64#endif
65#endif
66#ifndef PATH_MAX
67#ifdef WIN32
68#define PATH_MAX 512
69#else
70#include <sys/param.h>
71#endif
72#ifndef PATH_MAX
73#ifdef MAXPATHLEN
74#define PATH_MAX MAXPATHLEN
75#else
76#define PATH_MAX 1024
77#endif
78#endif
79#endif
80
81#define NUM_LOCALEDIR	64
82
83/* Splits a NUL terminated line into constituents, at colons and newline
84   characters. Leading whitespace is removed from constituents. The
85   constituents are stored at argv[0..argsize-1]. The number of stored
86   constituents (<= argsize) is returned. The line is destructively
87   modified. */
88static int
89parse_line(
90    char *line,
91    char **argv,
92    int argsize)
93{
94    int argc = 0;
95    char *p = line;
96
97    while (argc < argsize) {
98	while (isspace(*p)) {
99	    ++p;
100	}
101	if (*p == '\0') {
102	    break;
103	}
104	argv[argc++] = p;
105	while (*p != ':' && *p != '\n' && *p != '\0') {
106	    ++p;
107	}
108	if (*p == '\0') {
109	    break;
110	}
111	*p++ = '\0';
112    }
113
114    return argc;
115}
116
117#ifdef __UNIXOS2__
118
119/* fg021216: entries in locale files are separated by colons while under
120   OS/2, path entries are separated by semicolon, so we need two functions */
121
122static int
123parse_line1(
124    char *line,
125    char **argv,
126    int argsize)
127{
128    int argc = 0;
129    char *p = line;
130
131    while (argc < argsize) {
132	while (isspace(*p)) {
133	    ++p;
134	}
135	if (*p == '\0') {
136	    break;
137	}
138	argv[argc++] = p;
139	while (*p != ';' && *p != '\n' && *p != '\0') {
140	    ++p;
141	}
142	if (*p == '\0') {
143	    break;
144	}
145	*p++ = '\0';
146    }
147
148    return argc;
149}
150#elif defined(WIN32)
151
152/* this is parse_line but skips drive letters at the beginning of the entry */
153static int
154parse_line1(
155    char *line,
156    char **argv,
157    int argsize)
158{
159    int argc = 0;
160    char *p = line;
161
162    while (argc < argsize) {
163	while (isspace(*p)) {
164	    ++p;
165	}
166	if (*p == '\0') {
167	    break;
168	}
169	argv[argc++] = p;
170        if (isalpha(*p) && p[1] == ':') {
171            p+= 2; /* skip drive letters */
172        }
173	while (*p != ':' && *p != '\n' && *p != '\0') {
174	    ++p;
175	}
176	if (*p == '\0') {
177	    break;
178	}
179	*p++ = '\0';
180    }
181
182    return argc;
183}
184
185#endif   /* __UNIXOS2__ */
186
187/* Splits a colon separated list of directories, and returns the constituent
188   paths (without trailing slash). At most argsize constituents are stored
189   at argv[0..argsize-1]. The number of stored constituents is returned. */
190static int
191_XlcParsePath(
192    char *path,
193    char **argv,
194    int argsize)
195{
196    char *p = path;
197    int n, i;
198
199#if !defined(__UNIXOS2__) && !defined(WIN32)
200    n = parse_line(path, argv, argsize);
201#else
202    n = parse_line1(path, argv, argsize);
203#endif
204    for (i = 0; i < n; ++i) {
205	int len;
206	p = argv[i];
207	len = strlen(p);
208	if (len > 0 && p[len - 1] == '/') {
209	    /* eliminate trailing slash */
210	    p[len - 1] = '\0';
211	}
212    }
213    return n;
214}
215
216#ifndef XLOCALEDIR
217#define XLOCALEDIR "/usr/lib/X11/locale"
218#endif
219
220void
221xlocaledir(
222    char *buf,
223    int buf_len)
224{
225    char *p = buf;
226    int len = 0;
227
228#ifndef NO_XLOCALEDIR
229    char *dir;
230    int priv = 1;
231
232    dir = getenv("XLOCALEDIR");
233
234    if (dir) {
235#ifndef WIN32
236	/*
237	 * Only use the user-supplied path if the process isn't priviledged.
238	 */
239	if (getuid() == geteuid() && getgid() == getegid()) {
240#if defined(HASSETUGID)
241	    priv = issetugid();
242#elif defined(HASGETRESUID)
243	    {
244		uid_t ruid, euid, suid;
245		gid_t rgid, egid, sgid;
246		if ((getresuid(&ruid, &euid, &suid) == 0) &&
247		    (getresgid(&rgid, &egid, &sgid) == 0))
248		    priv = (euid != suid) || (egid != sgid);
249	    }
250#else
251	    /*
252	     * If there are saved ID's the process might still be priviledged
253	     * even though the above test succeeded.  If issetugid() and
254	     * getresgid() aren't available, test this by trying to set
255	     * euid to 0.
256	     *
257	     * Note: this only protects setuid-root clients.  It doesn't
258	     * protect other setuid or any setgid clients.  If this tradeoff
259	     * isn't acceptable, set DisableXLocaleDirEnv to YES in host.def.
260	     */
261	    unsigned int oldeuid;
262	    oldeuid = geteuid();
263	    if (seteuid(0) != 0) {
264		priv = 0;
265	    } else {
266		if (seteuid(oldeuid) == -1) {
267		    /* XXX ouch, coudn't get back to original uid
268		     what can we do ??? */
269		    _exit(127);
270		}
271		priv = 1;
272	    }
273#endif
274	}
275#else
276	priv = 0;
277#endif
278	if (!priv) {
279	    len = strlen(dir);
280	    strncpy(p, dir, buf_len);
281	    if (len < buf_len) {
282	        p[len++] = LC_PATHDELIM;
283	        p += len;
284	    }
285	}
286    }
287#endif /* NO_XLOCALEDIR */
288
289    if (len < buf_len)
290#ifndef __UNIXOS2__
291      strncpy(p, XLOCALEDIR, buf_len - len);
292#else
293      strncpy(p,__XOS2RedirRoot(XLOCALEDIR), buf_len - len);
294#endif
295    buf[buf_len-1] = '\0';
296}
297
298static void
299xlocalelibdir(
300    char *buf,
301    int buf_len)
302{
303    char *p = buf;
304    int len = 0;
305
306#ifndef NO_XLOCALEDIR
307    char *dir;
308    int priv = 1;
309
310    dir = getenv("XLOCALELIBDIR");
311
312    if (dir) {
313#ifndef WIN32
314	/*
315	 * Only use the user-supplied path if the process isn't priviledged.
316	 */
317	if (getuid() == geteuid() && getgid() == getegid()) {
318#if defined(HASSETUGID)
319	    priv = issetugid();
320#elif defined(HASGETRESUID)
321	    {
322		uid_t ruid, euid, suid;
323		gid_t rgid, egid, sgid;
324		if ((getresuid(&ruid, &euid, &suid) == 0) &&
325		    (getresgid(&rgid, &egid, &sgid) == 0))
326		    priv = (euid != suid) || (egid != sgid);
327	    }
328#else
329	    /*
330	     * If there are saved ID's the process might still be priviledged
331	     * even though the above test succeeded.  If issetugid() and
332	     * getresgid() aren't available, test this by trying to set
333	     * euid to 0.
334	     *
335	     * Note: this only protects setuid-root clients.  It doesn't
336	     * protect other setuid or any setgid clients.  If this tradeoff
337	     * isn't acceptable, set DisableXLocaleDirEnv to YES in host.def.
338	     */
339	    unsigned int oldeuid;
340	    oldeuid = geteuid();
341	    if (seteuid(0) != 0) {
342		priv = 0;
343	    } else {
344		if (seteuid(oldeuid) == -1) {
345		    /* XXX ouch, coudn't get back to original uid
346		     what can we do ??? */
347		    _exit(127);
348		}
349		priv = 1;
350	    }
351#endif
352	}
353#else
354	priv = 0;
355#endif
356	if (!priv) {
357	    len = strlen(dir);
358	    strncpy(p, dir, buf_len);
359	    if (len < buf_len) {
360	        p[len++] = LC_PATHDELIM;
361	        p += len;
362	    }
363	}
364    }
365#endif /* NO_XLOCALEDIR */
366
367    if (len < buf_len)
368#ifndef __UNIXOS2__
369      strncpy(p, XLOCALELIBDIR, buf_len - len);
370#else
371      strncpy(p,__XOS2RedirRoot(XLOCALELIBDIR), buf_len - len);
372#endif
373    buf[buf_len-1] = '\0';
374}
375
376/* Mapping direction */
377typedef enum {
378  LtoR,		/* Map first field to second field */
379  RtoL		/* Map second field to first field */
380} MapDirection;
381
382static char *
383resolve_name(
384    const char *lc_name,
385    char *file_name,
386    MapDirection direction)
387{
388    FILE *fp;
389    char buf[XLC_BUFSIZE], *name = NULL;
390
391    fp = _XFopenFile (file_name, "r");
392    if (fp == NULL)
393	return NULL;
394
395    while (fgets(buf, XLC_BUFSIZE, fp) != NULL) {
396	char *p = buf;
397	int n;
398	char *args[2], *from, *to;
399#ifdef __UNIXOS2__  /* Take out CR under OS/2 */
400	int len;
401
402	len = strlen(p);
403	if (len > 1) {
404	    if (*(p+len-2) == '\r' && *(p+len-1) == '\n') {
405		*(p+len-2) = '\n';
406		*(p+len-1) = '\0';
407	    }
408	}
409#endif
410	while (isspace(*p)) {
411	    ++p;
412	}
413	if (iscomment(*p)) {
414	    continue;
415	}
416	n = parse_line(p, args, 2);		/* get first 2 fields */
417	if (n != 2) {
418	    continue;
419	}
420	if (direction == LtoR) {
421	    from = args[0], to = args[1];	/* left to right */
422	} else {
423	    from = args[1], to = args[0];	/* right to left */
424	}
425	if (! strcmp(from, lc_name)) {
426	    name = Xmalloc(strlen(to) + 1);
427	    if (name != NULL) {
428		strcpy(name, to);
429	    }
430	    break;
431	}
432    }
433    fclose(fp);
434    return name;
435}
436
437#define	c_tolower(ch)	((ch) >= 'A' && (ch) <= 'Z' ? (ch) - 'A' + 'a' : (ch))
438
439static char *
440lowercase(
441    char *dst,
442    const char *src)
443{
444    const char *s;
445    char *t;
446
447    for (s = src, t = dst; *s; ++s, ++t)
448	*t = c_tolower(*s);
449    *t = '\0';
450    return dst;
451}
452
453/*
454 * normalize_lcname(): remove any '_' and '-' and convert any character
455 * to lower case after the <language>_<territory> part. If result is identical
456 * to argument, free result and
457 * return NULL.
458 */
459static char *
460normalize_lcname (const char *name)
461{
462    char *p, *ret;
463    const char *tmp = name;
464
465    p = ret = Xmalloc(strlen(name) + 1);
466    if (!p)
467	return NULL;
468
469    if (tmp) {
470	while (*tmp && *tmp != '.' && *tmp != '@')
471	    *p++ = *tmp++;
472	while (*tmp) {
473	    if (*tmp != '-')
474		*p++ = c_tolower(*tmp);
475	    tmp++;
476	}
477    }
478    *p = '\0';
479
480    if (strcmp(ret, name) == 0) {
481	Xfree(ret);
482	return NULL;
483    }
484
485    return ret;
486}
487
488/************************************************************************/
489char *
490_XlcFileName(
491    XLCd lcd,
492    const char *category)
493{
494    char *siname;
495    char cat[XLC_BUFSIZE], dir[XLC_BUFSIZE];
496    int i, n;
497    char *args[NUM_LOCALEDIR];
498    char *file_name = NULL;
499
500    if (lcd == (XLCd)NULL)
501	return NULL;
502
503    siname = XLC_PUBLIC(lcd, siname);
504
505    if (category)
506	lowercase(cat, category);
507    else
508	cat[0] = '\0';
509    xlocaledir(dir,XLC_BUFSIZE);
510    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
511    for (i = 0; i < n; ++i) {
512	char buf[PATH_MAX], *name;
513
514	name = NULL;
515	if ((5 + (args[i] ? strlen (args[i]) : 0) + strlen(cat)) < PATH_MAX) {
516	    sprintf(buf, "%s/%s.dir", args[i], cat);
517	    name = resolve_name(siname, buf, RtoL);
518	}
519	if (name == NULL) {
520	    continue;
521	}
522	if (*name == '/') {
523	    /* supposed to be absolute path name */
524	    file_name = name;
525	} else {
526	    file_name = Xmalloc(2 + (args[i] ? strlen (args[i]) : 0) +
527				(name ? strlen (name) : 0));
528	    if (file_name != NULL)
529		sprintf(file_name, "%s/%s", args[i], name);
530	    Xfree(name);
531	}
532	if (isreadable(file_name)) {
533	    break;
534	}
535	Xfree(file_name);
536	file_name = NULL;
537	/* Then, try with next dir */
538    }
539    return file_name;
540}
541
542/************************************************************************/
543#ifndef LOCALE_ALIAS
544#define LOCALE_ALIAS    "locale.alias"
545#endif
546
547int
548_XlcResolveLocaleName(
549    const char* lc_name,
550    XLCdPublicPart* pub)
551{
552    char dir[PATH_MAX], buf[PATH_MAX], *name = NULL;
553    char *dst;
554    int i, n, sinamelen;
555    char *args[NUM_LOCALEDIR];
556    static const char locale_alias[] = LOCALE_ALIAS;
557    char *tmp_siname;
558    char *nlc_name = NULL;
559
560    xlocaledir (dir, PATH_MAX);
561    n = _XlcParsePath(dir, args, NUM_LOCALEDIR);
562    for (i = 0; i < n; ++i) {
563	if ((2 + (args[i] ? strlen (args[i]) : 0) +
564	    strlen (locale_alias)) < PATH_MAX) {
565	    sprintf (buf, "%s/%s", args[i], locale_alias);
566	    name = resolve_name (lc_name, buf, LtoR);
567	    if (!name) {
568		if (!nlc_name)
569		    nlc_name = normalize_lcname(lc_name);
570		if (nlc_name)
571		    name = resolve_name (nlc_name, buf, LtoR);
572	    }
573	}
574	if (name != NULL) {
575	    break;
576	}
577    }
578    if (nlc_name) Xfree(nlc_name);
579
580    if (name == NULL) {
581	/* vendor locale name == Xlocale name, no expansion of alias */
582	pub->siname = Xmalloc (strlen (lc_name) + 1);
583	strcpy (pub->siname, lc_name);
584    } else {
585	pub->siname = name;
586    }
587
588    sinamelen = strlen (pub->siname);
589    if (sinamelen == 1 && pub->siname[0] == 'C') {
590	pub->language = pub->siname;
591	pub->territory = pub->codeset = NULL;
592	return 1;
593    }
594
595    /*
596     * pub->siname is in the format <lang>_<terr>.<codeset>, typical would
597     * be "en_US.ISO8859-1", "en_US.utf8", "ru_RU.KOI-8", or ja_JP.SJIS,
598     * although it could be ja.SJIS too.
599     */
600    tmp_siname = Xrealloc (pub->siname, 2 * (sinamelen + 1));
601    if (tmp_siname == NULL) {
602	return 0;
603    }
604    pub->siname = tmp_siname;
605
606    /* language */
607    dst = &pub->siname[sinamelen + 1];
608    strcpy (dst, pub->siname);
609    pub->language = dst;
610
611    /* territory */
612    dst = strchr (dst, '_');
613    if (dst) {
614	*dst = '\0';
615	pub->territory = ++dst;
616    } else
617	dst = &pub->siname[sinamelen + 1];
618
619    /* codeset */
620    dst = strchr (dst, '.');
621    if (dst) {
622	*dst = '\0';
623	pub->codeset = ++dst;
624    }
625
626    return (pub->siname[0] != '\0') ? 1 : 0;
627}
628
629/************************************************************************/
630int
631_XlcResolveI18NPath(char *buf, int buf_len)
632{
633    if (buf != NULL) {
634	xlocaledir(buf, buf_len);
635    }
636    return 1;
637}
638
639char *
640_XlcLocaleDirName(char *dir_name, size_t dir_len, char *lc_name)
641{
642    char dir[PATH_MAX], buf[PATH_MAX], *name = NULL;
643    int i, n;
644    char *args[NUM_LOCALEDIR];
645    static char locale_alias[] = LOCALE_ALIAS;
646    char *target_name = (char*)0;
647    char *target_dir = (char*)0;
648    char *nlc_name = NULL;
649    static char*  last_dir_name = 0;
650    static size_t last_dir_len = 0;
651    static char*  last_lc_name = 0;
652
653    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
654       && dir_len >= last_dir_len) {
655        strcpy (dir_name, last_dir_name);
656        return dir_name;
657    }
658
659    xlocaledir (dir, PATH_MAX);
660    n = _XlcParsePath(dir, args, 256);
661    for (i = 0; i < n; ++i) {
662
663	if ((2 + (args[i] ? strlen(args[i]) : 0) +
664 	     strlen(locale_alias)) < PATH_MAX) {
665 	    sprintf (buf, "%s/%s", args[i], locale_alias);
666 	    name = resolve_name(lc_name, buf, LtoR);
667	    if (!name) {
668		if (!nlc_name)
669		    nlc_name = normalize_lcname(lc_name);
670		if (nlc_name)
671		    name = resolve_name (nlc_name, buf, LtoR);
672	    }
673 	}
674
675 	/* If name is not an alias, use lc_name for locale.dir search */
676 	if (name == NULL)
677 	    name = lc_name;
678
679 	/* look at locale.dir */
680
681 	target_dir = args[i];
682 	if (!target_dir) {
683 	    /* something wrong */
684 	    if (name != lc_name)
685 		Xfree(name);
686 	    continue;
687 	}
688 	if ((1 + strlen (target_dir) + strlen("locale.dir")) < PATH_MAX) {
689 	    sprintf(buf, "%s/locale.dir", target_dir);
690 	    target_name = resolve_name(name, buf, RtoL);
691 	}
692 	if (name != lc_name)
693 	    Xfree(name);
694 	if (target_name != NULL) {
695 	    char *p = 0;
696 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
697 		*p = '\0';
698 		break;
699 	    }
700 	    Xfree(target_name);
701 	    target_name = NULL;
702 	}
703 	name = NULL;
704    }
705    if (nlc_name) Xfree(nlc_name);
706
707    if (target_name == NULL) {
708 	/* vendor locale name == Xlocale name, no expansion of alias */
709 	target_dir = args[0];
710 	target_name = lc_name;
711    }
712    /* snprintf(dir_name, dir_len, "%s/%", target_dir, target_name); */
713    strncpy(dir_name, target_dir, dir_len - 1);
714    if (strlen(target_dir) >= dir_len - 1) {
715	dir_name[dir_len - 1] = '\0';
716    } else  {
717	strcat(dir_name, "/");
718	strncat(dir_name, target_name, dir_len - strlen(dir_name) - 1);
719	if (strlen(target_name) >= dir_len - strlen(dir_name) - 1)
720	    dir_name[dir_len - 1] = '\0';
721    }
722    if (target_name != lc_name)
723 	Xfree(target_name);
724
725    if (last_dir_name != 0)
726	Xfree (last_dir_name);
727    if (last_lc_name != 0)
728	Xfree (last_lc_name);
729    last_dir_len = strlen (dir_name) + 1;
730    last_dir_name = Xmalloc (last_dir_len);
731    strcpy (last_dir_name, dir_name);
732    last_lc_name = Xmalloc (strlen (lc_name) + 1);
733    strcpy (last_lc_name, lc_name);
734
735    return dir_name;
736}
737
738char *
739_XlcLocaleLibDirName(char *dir_name, size_t dir_len, char *lc_name)
740{
741    char dir[PATH_MAX], buf[PATH_MAX], *name = NULL;
742    int i, n;
743    char *args[NUM_LOCALEDIR];
744    static char locale_alias[] = LOCALE_ALIAS;
745    char *target_name = (char*)0;
746    char *target_dir = (char*)0;
747    char *nlc_name = NULL;
748    static char*  last_dir_name = 0;
749    static size_t last_dir_len = 0;
750    static char*  last_lc_name = 0;
751
752    if (last_lc_name != 0 && strcmp (last_lc_name, lc_name) == 0
753       && dir_len >= last_dir_len) {
754	strcpy (dir_name, last_dir_name);
755	return dir_name;
756    }
757
758    xlocalelibdir (dir, PATH_MAX);
759    n = _XlcParsePath(dir, args, 256);
760    for (i = 0; i < n; ++i) {
761
762	if ((2 + (args[i] ? strlen(args[i]) : 0) +
763 	     strlen(locale_alias)) < PATH_MAX) {
764 	    sprintf (buf, "%s/%s", args[i], locale_alias);
765 	    name = resolve_name(lc_name, buf, LtoR);
766	    if (!name) {
767		if (!nlc_name)
768		    nlc_name = normalize_lcname(lc_name);
769		if (nlc_name)
770		    name = resolve_name (nlc_name, buf, LtoR);
771	    }
772 	}
773
774 	/* If name is not an alias, use lc_name for locale.dir search */
775 	if (name == NULL)
776 	    name = lc_name;
777
778 	/* look at locale.dir */
779
780 	target_dir = args[i];
781 	if (!target_dir) {
782 	    /* something wrong */
783 	    if (name != lc_name)
784 		Xfree(name);
785 	    continue;
786 	}
787 	if ((1 + strlen (target_dir) + strlen("locale.dir")) < PATH_MAX) {
788 	    sprintf(buf, "%s/locale.dir", target_dir);
789 	    target_name = resolve_name(name, buf, RtoL);
790 	}
791 	if (name != lc_name)
792 	    Xfree(name);
793 	if (target_name != NULL) {
794 	    char *p = 0;
795 	    if ((p = strstr(target_name, "/XLC_LOCALE"))) {
796 		*p = '\0';
797 		break;
798 	    }
799 	    Xfree(target_name);
800 	    target_name = NULL;
801 	}
802 	name = NULL;
803    }
804    if (nlc_name) Xfree(nlc_name);
805
806    if (target_name == NULL) {
807 	/* vendor locale name == Xlocale name, no expansion of alias */
808 	target_dir = args[0];
809 	target_name = lc_name;
810    }
811    /* snprintf(dir_name, dir_len, "%s/%", target_dir, target_name); */
812    strncpy(dir_name, target_dir, dir_len - 1);
813    if (strlen(target_dir) >= dir_len - 1) {
814	dir_name[dir_len - 1] = '\0';
815    } else  {
816	strcat(dir_name, "/");
817	strncat(dir_name, target_name, dir_len - strlen(dir_name) - 1);
818	if (strlen(target_name) >= dir_len - strlen(dir_name) - 1)
819	    dir_name[dir_len - 1] = '\0';
820    }
821    if (target_name != lc_name)
822 	Xfree(target_name);
823
824    if (last_dir_name != 0)
825	Xfree (last_dir_name);
826    if (last_lc_name != 0)
827	Xfree (last_lc_name);
828    last_dir_len = strlen (dir_name) + 1;
829    last_dir_name = Xmalloc (last_dir_len);
830    strcpy (last_dir_name, dir_name);
831    last_lc_name = Xmalloc (strlen (lc_name) + 1);
832    strcpy (last_lc_name, lc_name);
833
834    return dir_name;
835}
836