Coverage Report

Created: 2026-09-28 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/canonicalize.c
Line
Count
Source
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
18
#include "canonicalize.h"
19
#include "pathnames.h"
20
#include "all-io.h"
21
#include "strutils.h"
22
#include "fileutils.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
 * The @prefix allows /sys to be mounted or stored outside the system root
31
 * (/prefix/sys/...).
32
 */
33
char *ul_canonicalize_dm_name_prefixed(const char *prefix, const char *ptname)
34
583
{
35
583
  FILE  *f;
36
583
  size_t  sz;
37
583
  char  path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL;
38
39
583
  if (!ptname || !*ptname)
40
0
    return NULL;
41
42
583
  if (!prefix)
43
583
    prefix = "";
44
45
583
  snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname);
46
583
  if (!(f = fopen(path, "r" UL_CLOEXECSTR)))
47
583
    return NULL;
48
49
  /* read "<name>\n" from sysfs */
50
0
  if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
51
0
    if (name[sz - 1] == '\n')
52
0
      name[sz - 1] = '\0';
53
54
0
    snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name);
55
56
0
    if ((prefix && *prefix) || access(path, F_OK) == 0)
57
0
      res = strdup(path);
58
0
  }
59
0
  fclose(f);
60
0
  return res;
61
583
}
62
63
char *ul_canonicalize_dm_name(const char *ptname)
64
583
{
65
583
  return ul_canonicalize_dm_name_prefixed(NULL, ptname);
66
583
}
67
68
static int is_dm_devname(char *canonical, char **name)
69
0
{
70
0
  struct stat sb;
71
0
  char *p = strrchr(canonical, '/');
72
73
0
  *name = NULL;
74
75
0
  if (!p
76
0
      || strncmp(p, "/dm-", 4) != 0
77
0
      || !isdigit(*(p + 4))
78
0
      || stat(canonical, &sb) != 0
79
0
      || !S_ISBLK(sb.st_mode))
80
0
    return 0;
81
82
0
  *name = p + 1;
83
0
  return 1;
84
0
}
85
86
/*
87
 * This function does not canonicalize the path! It just prepends CWD before a
88
 * relative path. If the path is no relative than returns NULL. The path does
89
 * not have to exist.
90
 */
91
char *ul_absolute_path(const char *path)
92
0
{
93
0
  char cwd[PATH_MAX], *res, *p;
94
0
  size_t psz, csz;
95
96
0
  if (!ul_is_relative_path(path)) {
97
0
    errno = EINVAL;
98
0
    return NULL;
99
0
  }
100
0
  if (!getcwd(cwd, sizeof(cwd)))
101
0
    return NULL;
102
103
  /* simple clean up */
104
0
  if (ul_startswith(path, "./"))
105
0
    path += 2;
106
0
  else if (strcmp(path, ".") == 0)
107
0
    path = NULL;
108
109
0
  if (!path || !*path)
110
0
    return strdup(cwd);
111
112
0
  csz = strlen(cwd);
113
0
  psz = strlen(path);
114
115
0
  p = res = malloc(csz + 1 + psz + 1);
116
0
  if (!res)
117
0
    return NULL;
118
119
0
  p = mempcpy(p, cwd, csz);
120
0
  *p++ = '/';
121
0
  memcpy(p, path, psz + 1);
122
123
0
  return res;
124
0
}
125
126
/*
127
 * Returns: <0 on error, 1 is cannot be canonicalized (errno is set); 0 on success
128
 */
129
static int __attribute__((nonnull(2)))
130
do_canonicalize(const char *path, char **result,
131
    void *data __attribute__((__unused__)))
132
0
{
133
0
  char *canonical, *dmname;
134
135
0
  *result = NULL;
136
137
0
  if (!path || !*path) {
138
0
    errno = EINVAL;
139
0
    return -errno;
140
0
  }
141
142
0
  errno = 0;
143
0
  canonical = realpath(path, NULL);
144
0
  if (!canonical)
145
0
    return 1;
146
147
0
  if (is_dm_devname(canonical, &dmname)) {
148
0
    char *dm = ul_canonicalize_dm_name(dmname);
149
0
    if (dm) {
150
0
      free(canonical);
151
0
      canonical = dm;
152
0
    }
153
0
  }
154
155
0
  if (canonical)
156
0
    *result = canonical;
157
0
  return 0;
158
0
}
159
160
/*
161
 * Always returns a newly allocated string or NULL in case of an error. An
162
 * unreachable path is not an error (!), and in this case, it just duplicates
163
 * @path.
164
 */
165
char *ul_canonicalize_path(const char *path)
166
0
{
167
0
  char *canonical = NULL;
168
169
0
  if (do_canonicalize(path, &canonical, NULL) == 1)
170
0
    return strdup(path);
171
172
0
  return canonical;
173
0
}
174
175
/*
176
 * Drop permissions (e.g., suid) and canonicalize the path. If the path is
177
 * unreadable (for example, due to missing permissions), it returns NULL.
178
 */
179
char *ul_canonicalize_path_restricted(const char *path)
180
0
{
181
  return ul_restricted_path_oper(path, do_canonicalize, NULL);
182
0
}
183
184
#ifdef TEST_PROGRAM_CANONICALIZE
185
int main(int argc, char **argv)
186
{
187
  char *p;
188
189
  if (argc < 2) {
190
    fprintf(stderr, "usage: %s <device>\n", argv[0]);
191
    exit(EXIT_FAILURE);
192
  }
193
194
  fprintf(stdout, "orig:            %s\n", argv[1]);
195
196
  p = ul_canonicalize_path(argv[1]);
197
  fprintf(stdout, "real:            %s\n", p);
198
  free(p);
199
200
  p = ul_canonicalize_path_restricted(argv[1]);
201
  fprintf(stdout, "real-restricted: %s\n", p);
202
  free(p);
203
204
  exit(EXIT_SUCCESS);
205
}
206
#endif