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