1/* Concatenate two arbitrary file names.
2
3   Copyright (C) 1996-2007, 2009-2020 Free Software Foundation, Inc.
4
5   This program is free software: you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 3 of the License, or
8   (at your option) any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17
18/* Written by Jim Meyering.  */
19
20#include <config.h>
21
22/* Specification.  */
23#include "filenamecat.h"
24
25#include <stdlib.h>
26#include <string.h>
27
28#include "dirname.h"
29
30#if ! HAVE_MEMPCPY && ! defined mempcpy
31# define mempcpy(D, S, N) ((void *) ((char *) memcpy (D, S, N) + (N)))
32#endif
33
34/* Concatenate two file name components, DIR and BASE, in
35   newly-allocated storage and return the result.
36   The resulting file name F is such that the commands "ls F" and "(cd
37   DIR; ls ./BASE)" refer to the same file.  If necessary, put
38   a separator between DIR and BASE in the result.  Typically this
39   separator is "/", but in rare cases it might be ".".
40   In any case, if BASE_IN_RESULT is non-NULL, set
41   *BASE_IN_RESULT to point to the copy of BASE at the end of the
42   returned concatenation.
43
44   If malloc fails, return NULL with errno set.  */
45
46char *
47mfile_name_concat (char const *dir, char const *base, char **base_in_result)
48{
49  char const *dirbase = last_component (dir);
50  size_t dirbaselen = base_len (dirbase);
51  size_t dirlen = dirbase - dir + dirbaselen;
52  size_t baselen = strlen (base);
53  char sep = '\0';
54  if (dirbaselen)
55    {
56      /* DIR is not a file system root, so separate with / if needed.  */
57      if (! ISSLASH (dir[dirlen - 1]) && ! ISSLASH (*base))
58        sep = '/';
59    }
60  else if (ISSLASH (*base))
61    {
62      /* DIR is a file system root and BASE begins with a slash, so
63         separate with ".".  For example, if DIR is "/" and BASE is
64         "/foo" then return "/./foo", as "//foo" would be wrong on
65         some POSIX systems.  A fancier algorithm could omit "." in
66         some cases but is not worth the trouble.  */
67      sep = '.';
68    }
69
70  char *p_concat = malloc (dirlen + (sep != '\0')  + baselen + 1);
71  if (p_concat == NULL)
72    return NULL;
73
74  {
75    char *p;
76
77    p = mempcpy (p_concat, dir, dirlen);
78    *p = sep;
79    p += sep != '\0';
80
81    if (base_in_result)
82      *base_in_result = p;
83
84    p = mempcpy (p, base, baselen);
85    *p = '\0';
86  }
87
88  return p_concat;
89}
90