Coverage Report

Created: 2025-06-22 06:56

/src/util-linux/lib/canonicalize.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * canonicalize.c -- canonicalize pathname by removing symlinks
3
 *
4
 * This file may be distributed under the terms of the
5
 * GNU Lesser General Public License.
6
 *
7
 * Copyright (C) 2009-2013 Karel Zak <kzak@redhat.com>
8
 */
9
#include <stdio.h>
10
#include <string.h>
11
#include <ctype.h>
12
#include <unistd.h>
13
#include <errno.h>
14
#include <stdlib.h>
15
#include <sys/types.h>
16
#include <sys/stat.h>
17
#include <sys/wait.h>
18
19
#include "canonicalize.h"
20
#include "pathnames.h"
21
#include "all-io.h"
22
#include "strutils.h"
23
24
/*
25
 * Converts private "dm-N" names to "/dev/mapper/<name>"
26
 *
27
 * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
28
 * provides the real DM device names in /sys/block/<ptname>/dm/name
29
 */
30
char *__canonicalize_dm_name(const char *prefix, const char *ptname)
31
0
{
32
0
  FILE  *f;
33
0
  size_t  sz;
34
0
  char  path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL;
35
36
0
  if (!ptname || !*ptname)
37
0
    return NULL;
38
39
0
  if (!prefix)
40
0
    prefix = "";
41
42
0
  snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname);
43
0
  if (!(f = fopen(path, "r" UL_CLOEXECSTR)))
44
0
    return NULL;
45
46
  /* read "<name>\n" from sysfs */
47
0
  if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
48
0
    name[sz - 1] = '\0';
49
0
    snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name);
50
51
0
    if ((prefix && *prefix) || access(path, F_OK) == 0)
52
0
      res = strdup(path);
53
0
  }
54
0
  fclose(f);
55
0
  return res;
56
0
}
57
58
char *canonicalize_dm_name(const char *ptname)
59
0
{
60
0
  return __canonicalize_dm_name(NULL, ptname);
61
0
}
62
63
static int is_dm_devname(char *canonical, char **name)
64
0
{
65
0
  struct stat sb;
66
0
  char *p = strrchr(canonical, '/');
67
68
0
  *name = NULL;
69
70
0
  if (!p
71
0
      || strncmp(p, "/dm-", 4) != 0
72
0
      || !isdigit(*(p + 4))
73
0
      || stat(canonical, &sb) != 0
74
0
      || !S_ISBLK(sb.st_mode))
75
0
    return 0;
76
77
0
  *name = p + 1;
78
0
  return 1;
79
0
}
80
81
/*
82
 * This function does not canonicalize the path! It just prepends CWD before a
83
 * relative path. If the path is no relative than returns NULL. The path does
84
 * not have to exist.
85
 */
86
char *absolute_path(const char *path)
87
0
{
88
0
  char cwd[PATH_MAX], *res, *p;
89
0
  size_t psz, csz;
90
91
0
  if (!is_relative_path(path)) {
92
0
    errno = EINVAL;
93
0
    return NULL;
94
0
  }
95
0
  if (!getcwd(cwd, sizeof(cwd)))
96
0
    return NULL;
97
98
  /* simple clean up */
99
0
  if (startswith(path, "./"))
100
0
    path += 2;
101
0
  else if (strcmp(path, ".") == 0)
102
0
    path = NULL;
103
104
0
  if (!path || !*path)
105
0
    return strdup(cwd);
106
107
0
  csz = strlen(cwd);
108
0
  psz = strlen(path);
109
110
0
  p = res = malloc(csz + 1 + psz + 1);
111
0
  if (!res)
112
0
    return NULL;
113
114
0
  p = mempcpy(p, cwd, csz);
115
0
  *p++ = '/';
116
0
  memcpy(p, path, psz + 1);
117
118
0
  return res;
119
0
}
120
121
char *canonicalize_path(const char *path)
122
0
{
123
0
  char *canonical, *dmname;
124
125
0
  if (!path || !*path)
126
0
    return NULL;
127
128
0
  canonical = realpath(path, NULL);
129
0
  if (!canonical)
130
0
    return strdup(path);
131
132
0
  if (is_dm_devname(canonical, &dmname)) {
133
0
    char *dm = canonicalize_dm_name(dmname);
134
0
    if (dm) {
135
0
      free(canonical);
136
0
      return dm;
137
0
    }
138
0
  }
139
140
0
  return canonical;
141
0
}
142
143
char *canonicalize_path_restricted(const char *path)
144
0
{
145
0
  char *canonical = NULL;
146
0
  int errsv = 0;
147
0
  int pipes[2];
148
0
  ssize_t len;
149
0
  pid_t pid;
150
151
0
  if (!path || !*path)
152
0
    return NULL;
153
154
0
  if (pipe(pipes) != 0)
155
0
    return NULL;
156
157
  /*
158
   * To accurately assume identity of getuid() we must use setuid()
159
   * but if we do that, we lose ability to reassume euid of 0, so
160
   * we fork to do the check to keep euid intact.
161
   */
162
0
  pid = fork();
163
0
  switch (pid) {
164
0
  case -1:
165
0
    close(pipes[0]);
166
0
    close(pipes[1]);
167
0
    return NULL;     /* fork error */
168
0
  case 0:
169
0
    close(pipes[0]);    /* close unused end */
170
0
    pipes[0] = -1;
171
0
    errno = 0;
172
173
0
    if (drop_permissions() != 0)
174
0
      canonical = NULL; /* failed */
175
0
    else {
176
0
      char *dmname = NULL;
177
178
0
      canonical = realpath(path, NULL);
179
0
      if (canonical && is_dm_devname(canonical, &dmname)) {
180
0
        char *dm = canonicalize_dm_name(dmname);
181
0
        if (dm) {
182
0
          free(canonical);
183
0
          canonical = dm;
184
0
        }
185
0
      }
186
0
    }
187
188
0
    len = canonical ? (ssize_t) strlen(canonical) :
189
0
              errno ? -errno : -EINVAL;
190
191
    /* send length or errno */
192
0
    write_all(pipes[1], (char *) &len, sizeof(len));
193
0
    if (canonical)
194
0
      write_all(pipes[1], canonical, len);
195
0
    _exit(0);
196
0
  default:
197
0
    break;
198
0
  }
199
200
0
  close(pipes[1]);    /* close unused end */
201
0
  pipes[1] = -1;
202
203
  /* read size or -errno */
204
0
  if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
205
0
    goto done;
206
0
  if (len < 0) {
207
0
    errsv = -len;
208
0
    goto done;
209
0
  }
210
211
0
  canonical = malloc(len + 1);
212
0
  if (!canonical) {
213
0
    errsv = ENOMEM;
214
0
    goto done;
215
0
  }
216
  /* read path */
217
0
  if (read_all(pipes[0], canonical, len) != len) {
218
0
    errsv = errno;
219
0
    goto done;
220
0
  }
221
0
  canonical[len] = '\0';
222
0
done:
223
0
  if (errsv) {
224
0
    free(canonical);
225
0
    canonical = NULL;
226
0
  }
227
0
  close(pipes[0]);
228
229
  /* We make a best effort to reap child */
230
0
  ignore_result( waitpid(pid, NULL, 0) );
231
232
0
  errno = errsv;
233
0
  return canonical;
234
0
}
235
236
237
#ifdef TEST_PROGRAM_CANONICALIZE
238
int main(int argc, char **argv)
239
{
240
  if (argc < 2) {
241
    fprintf(stderr, "usage: %s <device>\n", argv[0]);
242
    exit(EXIT_FAILURE);
243
  }
244
245
  fprintf(stdout, "orig: %s\n", argv[1]);
246
  fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
247
  exit(EXIT_SUCCESS);
248
}
249
#endif