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 ((l = read(ifd, info, st.st_size)) == -1) { 159 err = errno; 160 break; 161 } 162 163 /* Walk the data to check that is properly terminated. */ 164 165 off = offsetof(prfdinfo_t, pr_misc); 166 167 while (off <= l - sizeof (pr_misc_header_t)) { 168 pr_misc_header_t *misc; 169 170 misc = (pr_misc_header_t *)((uint8_t *)info + off); 171 172 if (misc->pr_misc_size == 0) { 173 /* Found terminator record */ 174 (void) close(ifd); 175 return (info); 176 } 177 178 /* Next record */ 179 off += misc->pr_misc_size; 180 } 181 } 182 183 (void) close(ifd); 184 free(info); 185 186 errno = err; 187 188 return (NULL); 189 } 190 191 typedef struct proc_fdinfo_misc_cbdata { 192 uint_t type; 193 const void *data; 194 size_t len; 195 } pfm_data_t; 196 197 static int 198 proc_fdinfo_misc_cb(uint_t type, const void *data, size_t len, void *datap) 199 { 200 pfm_data_t *cb = (pfm_data_t *)datap; 201 202 if (type == cb->type) { 203 cb->data = data; 204 cb->len = len; 205 return (1); 206 } 207 return (0); 208 } 209 210 const void * 211 proc_fdinfo_misc(const prfdinfo_t *info, uint_t type, size_t *buflen) 212 { 213 pfm_data_t cb; 214 215 cb.data = NULL; 216 cb.type = type; 217 218 (void) proc_fdinfowalk(info, proc_fdinfo_misc_cb, (void *)&cb); 219 220 if (cb.data != NULL) { 221 if (buflen != NULL) 222 *buflen = cb.len; 223 224 return (cb.data); 225 } 226 227 return (NULL); 228 } 229 230 static int 231 proc_fdinfo_dup_cb(uint_t type, const void *data, size_t len, void *datap) 232 { 233 size_t *sz = (size_t *)datap; 234 235 *sz += len + sizeof (pr_misc_header_t); 236 return (0); 237 } 238 239 240 prfdinfo_t * 241 proc_fdinfo_dup(const prfdinfo_t *old) 242 { 243 prfdinfo_t *new; 244 size_t sz = offsetof(prfdinfo_t, pr_misc); 245 246 /* Determine the size of the miscellaneous items */ 247 (void) proc_fdinfowalk(old, proc_fdinfo_dup_cb, (void *)&sz); 248 249 /* Add the size of the terminator record */ 250 sz += sizeof (pr_misc_header_t); 251 252 if ((new = calloc(1, sz)) == NULL) 253 return (NULL); 254 255 bcopy(old, new, sz); 256 257 return (new); 258 } 259 260 void 261 proc_fdinfo_free(prfdinfo_t *info) 262 { 263 free(info); 264 } 265 266 /* 267 * Convert a prfdinfo_core_t to prfdinfo_t 268 */ 269 int 270 proc_fdinfo_from_core(const prfdinfo_core_t *core, prfdinfo_t **infop) 271 { 272 prfdinfo_t *info; 273 size_t len, slen = 0; 274 275 len = offsetof(prfdinfo_t, pr_misc) + sizeof (pr_misc_header_t); 276 if (*core->pr_path != '\0') { 277 slen = strlen(core->pr_path) + 1; 278 len += PRFDINFO_ROUNDUP(slen) + sizeof (pr_misc_header_t); 279 } 280 281 if ((info = calloc(1, len)) == NULL) 282 return (-1); 283 284 *infop = info; 285 286 info->pr_fd = core->pr_fd; 287 info->pr_mode = core->pr_mode; 288 info->pr_uid = core->pr_uid; 289 info->pr_gid = core->pr_gid; 290 info->pr_major = core->pr_major; 291 info->pr_minor = core->pr_minor; 292 info->pr_rmajor = core->pr_rmajor; 293 info->pr_rminor = core->pr_rminor; 294 info->pr_size = core->pr_size; 295 info->pr_ino = core->pr_ino; 296 info->pr_fileflags = core->pr_fileflags; 297 info->pr_fdflags = core->pr_fdflags; 298 info->pr_offset = core->pr_offset; 299 300 if (slen != 0) { 301 pr_misc_header_t *misc; 302 303 misc = (pr_misc_header_t *)&info->pr_misc; 304 305 misc->pr_misc_size = sizeof (*misc) + PRFDINFO_ROUNDUP(slen); 306 misc->pr_misc_type = PR_PATHNAME; 307 misc++; 308 bcopy(core->pr_path, misc, slen); 309 } 310 311 return (0); 312 } 313 314 /* 315 * Convert a prfdinfo_t to prfdinfo_core_t 316 */ 317 int 318 proc_fdinfo_to_core(const prfdinfo_t *info, prfdinfo_core_t *core) 319 { 320 const char *path; 321 size_t pathl; 322 323 bzero(core, sizeof (*core)); 324 325 core->pr_fd = info->pr_fd; 326 core->pr_mode = info->pr_mode; 327 core->pr_uid = info->pr_uid; 328 core->pr_gid = info->pr_gid; 329 core->pr_major = info->pr_major; 330 core->pr_minor = info->pr_minor; 331 core->pr_rmajor = info->pr_rmajor; 332 core->pr_rminor = info->pr_rminor; 333 core->pr_size = info->pr_size; 334 core->pr_ino = info->pr_ino; 335 core->pr_fileflags = info->pr_fileflags; 336 core->pr_fdflags = info->pr_fdflags; 337 core->pr_offset = info->pr_offset; 338 339 path = proc_fdinfo_misc(info, PR_PATHNAME, &pathl); 340 if (path != NULL) { 341 /* 342 * Rather than provide a truncated path in the pr_path field 343 * just leave it empty if the path will not fit. 344 */ 345 if (pathl <= sizeof (core->pr_path) - 1) 346 bcopy(path, core->pr_path, pathl + 1); 347 } 348 349 return (0); 350 } 351