Coverage Report

Created: 2025-10-12 06:56

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