1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
14 */
15
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <sys/proc.h>
19 #include <sys/sysmacros.h>
20
21 #include <libgen.h>
22 #include <limits.h>
23 #include <alloca.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <strings.h>
27 #include <fcntl.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <dirent.h>
31
32 #include "Pcontrol.h"
33
34 /*
35 * Walk all file descriptors open for a process and call func() for each.
36 */
37 int
proc_fdwalk(pid_t pid,proc_fdwalk_f * func,void * arg)38 proc_fdwalk(pid_t pid, proc_fdwalk_f *func, void *arg)
39 {
40 struct dirent *dirent;
41 DIR *fddir;
42 char *dir;
43 int ret = 0;
44
45 if (asprintf(&dir, "%s/%d/fd", procfs_path, (int)pid) == -1)
46 return (-1);
47
48 if ((fddir = opendir(dir)) == NULL) {
49 free(dir);
50 return (-1);
51 }
52
53 free(dir);
54
55 while ((dirent = readdir(fddir)) != NULL) {
56 prfdinfo_t *info;
57 char *errptr;
58 int fd;
59
60 if (!isdigit(dirent->d_name[0]))
61 continue;
62
63 fd = (int)strtol(dirent->d_name, &errptr, 10);
64 if (errptr != NULL && *errptr != '\0')
65 continue;
66
67 if ((info = proc_get_fdinfo(pid, fd)) == NULL)
68 continue;
69
70 ret = func(info, arg);
71
72 free(info);
73
74 if (ret != 0)
75 break;
76 }
77
78 (void) closedir(fddir);
79 return (ret);
80 }
81
82 int
proc_fdinfowalk(const prfdinfo_t * info,proc_fdinfowalk_f * func,void * arg)83 proc_fdinfowalk(const prfdinfo_t *info, proc_fdinfowalk_f *func, void *arg)
84 {
85 off_t off = offsetof(prfdinfo_t, pr_misc);
86 int ret = 0;
87
88 for (;;) {
89 const pr_misc_header_t *misc;
90 uint_t type;
91 size_t size;
92
93 misc = (pr_misc_header_t *)((uint8_t *)info + off);
94
95 /* Found terminating record */
96 if (misc->pr_misc_size == 0)
97 break;
98
99 off += misc->pr_misc_size;
100
101 type = misc->pr_misc_type;
102 size = misc->pr_misc_size - sizeof (pr_misc_header_t);
103 misc++;
104
105 ret = func(type, misc, size, arg);
106
107 if (ret != 0)
108 break;
109 }
110
111 return (ret);
112 }
113
114 prfdinfo_t *
proc_get_fdinfo(pid_t pid,int fd)115 proc_get_fdinfo(pid_t pid, int fd)
116 {
117 prfdinfo_t *info = NULL;
118 char *fname;
119 uint_t retries;
120 int ifd, err = EIO;
121
122 if (asprintf(&fname, "%s/%d/fdinfo/%d",
123 procfs_path, (int)pid, fd) == -1) {
124 return (NULL);
125 }
126
127 if ((ifd = open(fname, O_RDONLY)) == -1) {
128 free(fname);
129 return (NULL);
130 }
131
132 free(fname);
133
134 /*
135 * There is a race between stat()-ing the file and reading from
136 * it where the size may change. To protect against that, we
137 * walk the returned data to ensure that it is properly
138 * terminated. If not, increase the buffer size and try again.
139 */
140
141 for (retries = 1; retries < 5; retries++) {
142 struct stat st;
143 off_t off;
144 size_t l;
145
146 if (fstat(ifd, &st) == -1) {
147 err = errno;
148 break;
149 }
150
151 st.st_size *= retries;
152
153 if ((info = reallocf(info, st.st_size)) == NULL) {
154 err = errno;
155 break;
156 }
157
158 if (lseek(ifd, 0, SEEK_SET) != 0 ||
159 (l = read(ifd, info, st.st_size)) == -1) {
160 err = errno;
161 break;
162 }
163
164 /* Walk the data to check that is properly terminated. */
165
166 off = offsetof(prfdinfo_t, pr_misc);
167
168 if (l < off + sizeof (pr_misc_header_t))
169 continue;
170
171 while (off <= l - sizeof (pr_misc_header_t)) {
172 pr_misc_header_t *misc;
173
174 misc = (pr_misc_header_t *)((uint8_t *)info + off);
175
176 if (misc->pr_misc_size == 0) {
177 /* Found terminator record */
178 (void) close(ifd);
179 return (info);
180 }
181
182 /* Next record */
183 off += misc->pr_misc_size;
184 }
185 }
186
187 (void) close(ifd);
188 free(info);
189
190 errno = err;
191
192 return (NULL);
193 }
194
195 typedef struct proc_fdinfo_misc_cbdata {
196 uint_t type;
197 const void *data;
198 size_t len;
199 } pfm_data_t;
200
201 static int
proc_fdinfo_misc_cb(uint_t type,const void * data,size_t len,void * datap)202 proc_fdinfo_misc_cb(uint_t type, const void *data, size_t len, void *datap)
203 {
204 pfm_data_t *cb = (pfm_data_t *)datap;
205
206 if (type == cb->type) {
207 cb->data = data;
208 cb->len = len;
209 return (1);
210 }
211 return (0);
212 }
213
214 const void *
proc_fdinfo_misc(const prfdinfo_t * info,uint_t type,size_t * buflen)215 proc_fdinfo_misc(const prfdinfo_t *info, uint_t type, size_t *buflen)
216 {
217 pfm_data_t cb;
218
219 cb.data = NULL;
220 cb.type = type;
221
222 (void) proc_fdinfowalk(info, proc_fdinfo_misc_cb, (void *)&cb);
223
224 if (cb.data != NULL) {
225 if (buflen != NULL)
226 *buflen = cb.len;
227
228 return (cb.data);
229 }
230
231 return (NULL);
232 }
233
234 static int
proc_fdinfo_dup_cb(uint_t type,const void * data,size_t len,void * datap)235 proc_fdinfo_dup_cb(uint_t type, const void *data, size_t len, void *datap)
236 {
237 size_t *sz = (size_t *)datap;
238
239 *sz += len + sizeof (pr_misc_header_t);
240 return (0);
241 }
242
243
244 prfdinfo_t *
proc_fdinfo_dup(const prfdinfo_t * old)245 proc_fdinfo_dup(const prfdinfo_t *old)
246 {
247 prfdinfo_t *new;
248 size_t sz = offsetof(prfdinfo_t, pr_misc);
249
250 /* Determine the size of the miscellaneous items */
251 (void) proc_fdinfowalk(old, proc_fdinfo_dup_cb, (void *)&sz);
252
253 /* Add the size of the terminator record */
254 sz += sizeof (pr_misc_header_t);
255
256 if ((new = calloc(1, sz)) == NULL)
257 return (NULL);
258
259 bcopy(old, new, sz);
260
261 return (new);
262 }
263
264 void
proc_fdinfo_free(prfdinfo_t * info)265 proc_fdinfo_free(prfdinfo_t *info)
266 {
267 free(info);
268 }
269
270 /*
271 * Convert a prfdinfo_core_t to prfdinfo_t
272 */
273 int
proc_fdinfo_from_core(const prfdinfo_core_t * core,prfdinfo_t ** infop)274 proc_fdinfo_from_core(const prfdinfo_core_t *core, prfdinfo_t **infop)
275 {
276 prfdinfo_t *info;
277 size_t len, slen = 0;
278
279 len = offsetof(prfdinfo_t, pr_misc) + sizeof (pr_misc_header_t);
280 if (*core->pr_path != '\0') {
281 slen = strlen(core->pr_path) + 1;
282 len += PRFDINFO_ROUNDUP(slen) + sizeof (pr_misc_header_t);
283 }
284
285 if ((info = calloc(1, len)) == NULL)
286 return (-1);
287
288 *infop = info;
289
290 info->pr_fd = core->pr_fd;
291 info->pr_mode = core->pr_mode;
292 info->pr_uid = core->pr_uid;
293 info->pr_gid = core->pr_gid;
294 info->pr_major = core->pr_major;
295 info->pr_minor = core->pr_minor;
296 info->pr_rmajor = core->pr_rmajor;
297 info->pr_rminor = core->pr_rminor;
298 info->pr_size = core->pr_size;
299 info->pr_ino = core->pr_ino;
300 info->pr_fileflags = core->pr_fileflags;
301 info->pr_fdflags = core->pr_fdflags;
302 info->pr_offset = core->pr_offset;
303
304 if (slen != 0) {
305 pr_misc_header_t *misc;
306
307 misc = (pr_misc_header_t *)&info->pr_misc;
308
309 misc->pr_misc_size = sizeof (*misc) + PRFDINFO_ROUNDUP(slen);
310 misc->pr_misc_type = PR_PATHNAME;
311 misc++;
312 bcopy(core->pr_path, misc, slen);
313 }
314
315 return (0);
316 }
317
318 /*
319 * Convert a prfdinfo_t to prfdinfo_core_t
320 */
321 int
proc_fdinfo_to_core(const prfdinfo_t * info,prfdinfo_core_t * core)322 proc_fdinfo_to_core(const prfdinfo_t *info, prfdinfo_core_t *core)
323 {
324 const char *path;
325 size_t pathl;
326
327 bzero(core, sizeof (*core));
328
329 core->pr_fd = info->pr_fd;
330 core->pr_mode = info->pr_mode;
331 core->pr_uid = info->pr_uid;
332 core->pr_gid = info->pr_gid;
333 core->pr_major = info->pr_major;
334 core->pr_minor = info->pr_minor;
335 core->pr_rmajor = info->pr_rmajor;
336 core->pr_rminor = info->pr_rminor;
337 core->pr_size = info->pr_size;
338 core->pr_ino = info->pr_ino;
339 core->pr_fileflags = info->pr_fileflags;
340 core->pr_fdflags = info->pr_fdflags;
341 core->pr_offset = info->pr_offset;
342
343 path = proc_fdinfo_misc(info, PR_PATHNAME, &pathl);
344 if (path != NULL) {
345 /*
346 * Rather than provide a truncated path in the pr_path field
347 * just leave it empty if the path will not fit.
348 */
349 if (pathl <= sizeof (core->pr_path) - 1)
350 bcopy(path, core->pr_path, pathl + 1);
351 }
352
353 return (0);
354 }
355