xref: /illumos-gate/usr/src/lib/libproc/common/proc_fd.c (revision 78801af7286cd73dbc996d470f789e75993cf15d)
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
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
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 *
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
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 *
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
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 *
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
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
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
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