1 /* 2 * ntp_ppsdev.c - PPS-device support 3 * 4 * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project. 5 * The contents of 'html/copyright.html' apply. 6 * --------------------------------------------------------------------- 7 * Helper code to work around (or with) a Linux 'specialty': PPS devices 8 * are created via attaching the PPS line discipline to a TTY. This 9 * creates new pps devices, and the PPS API is *not* available through 10 * the original TTY fd. 11 * 12 * Findig the PPS device associated with a TTY is possible but needs 13 * quite a bit of file system traversal & lookup in the 'sysfs' tree. 14 * 15 * The code below does the job for kernel versions 4 & 5, and will 16 * probably work for older and newer kernels, too... and in any case, if 17 * the device or symlink to the PPS device with the given name exists, 18 * it will take precedence anyway. 19 * --------------------------------------------------------------------- 20 */ 21 #ifdef __linux__ 22 # define _GNU_SOURCE 23 #endif 24 25 #include "config.h" 26 27 #include "ntpd.h" 28 29 #ifdef REFCLOCK 30 31 #if defined(HAVE_UNISTD_H) 32 # include <unistd.h> 33 #endif 34 #if defined(HAVE_FCNTL_H) 35 # include <fcntl.h> 36 #endif 37 38 #include <stdlib.h> 39 40 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 41 #if defined(__linux__) && defined(HAVE_OPENAT) && defined(HAVE_FDOPENDIR) 42 #define WITH_PPSDEV_MATCH 43 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 44 45 #include <stdio.h> 46 #include <dirent.h> 47 #include <string.h> 48 #include <errno.h> 49 50 #include <sys/ioctl.h> 51 #include <sys/types.h> 52 #include <sys/stat.h> 53 #include <sys/sysmacros.h> 54 #include <linux/tty.h> 55 56 typedef int BOOL; 57 #ifndef TRUE 58 # define TRUE 1 59 #endif 60 #ifndef FALSE 61 # define FALSE 0 62 #endif 63 64 static const int OModeF = O_CLOEXEC|O_RDONLY|O_NOCTTY; 65 static const int OModeD = O_CLOEXEC|O_RDONLY|O_DIRECTORY; 66 67 /* ------------------------------------------------------------------ */ 68 /* extended directory stream 69 */ 70 typedef struct { 71 int dfd; /* file descriptor for dir for 'openat()' */ 72 DIR *dir; /* directory stream for iteration */ 73 } XDIR; 74 75 static void 76 xdirClose( 77 XDIR *pxdir) 78 { 79 if (NULL != pxdir->dir) 80 closedir(pxdir->dir); /* closes the internal FD, too! */ 81 else if (-1 != pxdir->dfd) 82 close(pxdir->dfd); /* otherwise _we_ have to do it */ 83 pxdir->dfd = -1; 84 pxdir->dir = NULL; 85 } 86 87 static BOOL 88 xdirOpenAt( 89 XDIR *pxdir, 90 int fdo , 91 const char *path ) 92 { 93 /* Officially, the directory stream owns the file discriptor it 94 * received via 'fdopendir()'. But for the purpose of 'openat()' 95 * it's ok to keep the value around -- even if we should do 96 * _absolutely_nothing_ with it apart from using it as a path 97 * reference! 98 */ 99 pxdir->dir = NULL; 100 if (-1 == (pxdir->dfd = openat(fdo, path, OModeD))) 101 goto fail; 102 if (NULL == (pxdir->dir = fdopendir(pxdir->dfd))) 103 goto fail; 104 return TRUE; 105 106 fail: 107 xdirClose(pxdir); 108 return FALSE; 109 } 110 111 /* -------------------------------------------------------------------- 112 * read content of a file (with a size limit) into a piece of allocated 113 * memory and trim any trailing whitespace. 114 * 115 * The issue here is that several files in the 'sysfs' tree claim a size 116 * of 4096 bytes when you 'stat' them -- but reading gives EOF after a 117 * few chars. (I *can* understand why the kernel takes this shortcut. 118 * it's just a bit unwieldy...) 119 */ 120 static char* 121 readFileAt( 122 int rfd , 123 const char *path) 124 { 125 struct stat sb; 126 char *ret = NULL; 127 ssize_t rdlen; 128 int dfd; 129 130 if (-1 == (dfd = openat(rfd, path, OModeF)) || -1 == fstat(dfd, &sb)) 131 goto fail; 132 if ((sb.st_size > 0x2000) || (NULL == (ret = malloc(sb.st_size + 1)))) 133 goto fail; 134 if (1 > (rdlen = read(dfd, ret, sb.st_size))) 135 goto fail; 136 close(dfd); 137 138 while (rdlen > 0 && ret[rdlen - 1] <= ' ') 139 --rdlen; 140 ret[rdlen] = '\0'; 141 return ret; 142 143 fail: 144 free(ret); 145 if (-1 != dfd) 146 close(dfd); 147 return NULL; 148 } 149 150 /* -------------------------------------------------------------------- 151 * Scan the "/dev" directory for a device with a given major and minor 152 * device id. Return the path if found. 153 */ 154 static char* 155 findDevByDevId( 156 dev_t rdev) 157 { 158 struct stat sb; 159 struct dirent *dent; 160 XDIR xdir; 161 char *name = NULL; 162 163 if (!xdirOpenAt(&xdir, AT_FDCWD, "/dev")) 164 goto done; 165 166 while (!name && (dent = readdir(xdir.dir))) { 167 if (-1 == fstatat(xdir.dfd, dent->d_name, 168 &sb, AT_SYMLINK_NOFOLLOW)) 169 continue; 170 if (!S_ISCHR(sb.st_mode)) 171 continue; 172 if (sb.st_rdev == rdev) { 173 if (-1 == asprintf(&name, "/dev/%s", dent->d_name)) 174 name = NULL; 175 } 176 } 177 xdirClose(&xdir); 178 179 done: 180 return name; 181 } 182 183 /* -------------------------------------------------------------------- 184 * Get the mofor:minor device id for a character device file descriptor 185 */ 186 static BOOL 187 getCharDevId( 188 int fd , 189 dev_t *out, 190 struct stat *psb) 191 { 192 BOOL rc = FALSE; 193 struct stat sb; 194 195 if (NULL == psb) 196 psb = &sb; 197 if (-1 != fstat(fd, psb)) { 198 rc = S_ISCHR(psb->st_mode); 199 if (rc) 200 *out = psb->st_rdev; 201 else 202 errno = EINVAL; 203 } 204 return rc; 205 } 206 207 /* -------------------------------------------------------------------- 208 * given the dir-fd of a pps instance dir in the linux sysfs tree, get 209 * the device IDs for the PPS device and the associated TTY. 210 */ 211 static BOOL 212 getPpsTuple( 213 int fdDir, 214 dev_t *pTty, 215 dev_t *pPps) 216 { 217 BOOL rc = FALSE; 218 unsigned long dmaj, dmin; 219 struct stat sb; 220 char *bufp, *endp, *scan; 221 222 /* 'path' contains the primary path to the associated TTY: 223 * we 'stat()' for the device id in 'st_rdev'. 224 */ 225 if (NULL == (bufp = readFileAt(fdDir, "path"))) 226 goto done; 227 if ((-1 == stat(bufp, &sb)) || !S_ISCHR(sb.st_mode)) 228 goto done; 229 *pTty = sb.st_rdev; 230 free(bufp); 231 232 /* 'dev' holds the device ID of the PPS device as 'major:minor' 233 * in text format. *sigh* couldn't that simply be the name of 234 * the PPS device itself, as in 'path' above??? But nooooo.... 235 */ 236 if (NULL == (bufp = readFileAt(fdDir, "dev"))) 237 goto done; 238 dmaj = strtoul((scan = bufp), &endp, 10); 239 if ((endp == scan) || (*endp != ':') || (dmaj >= 256)) 240 goto done; 241 dmin = strtoul((scan = endp + 1), &endp, 10); 242 if ((endp == scan) || (*endp >= ' ') || (dmin >= 256)) 243 goto done; 244 *pPps = makedev((unsigned int)dmaj, (unsigned int)dmin); 245 rc = TRUE; 246 247 done: 248 free(bufp); 249 return rc; 250 } 251 252 /* -------------------------------------------------------------------- 253 * for a given (TTY) device id, lookup the corresponding PPS device id 254 * by processing the contents of the kernel sysfs tree. 255 * Returns false if no such PS device can be found; otherwise set the 256 * ouput parameter to the PPS dev id and return true... 257 */ 258 static BOOL 259 findPpsDevId( 260 dev_t ttyId , 261 dev_t *pPpsId) 262 { 263 BOOL found = FALSE; 264 XDIR ClassDir; 265 struct dirent *dent; 266 dev_t othId, ppsId; 267 int fdDevDir; 268 269 if (!xdirOpenAt(&ClassDir, AT_FDCWD, "/sys/class/pps")) 270 goto done; 271 272 while (!found && (dent = readdir(ClassDir.dir))) { 273 274 /* If the entry is not a referring to a PPS device or 275 * if we can't open the directory for reading, skipt it: 276 */ 277 if (strncmp("pps", dent->d_name, 3)) 278 continue; 279 fdDevDir = openat(ClassDir.dfd, dent->d_name, OModeD); 280 if (-1 == fdDevDir) 281 continue; 282 283 /* get the data and check if device ID for the TTY 284 * is what we're looking for: 285 */ 286 found = getPpsTuple(fdDevDir, &othId, &ppsId) 287 && (ttyId == othId); 288 close(fdDevDir); 289 } 290 291 xdirClose(&ClassDir); 292 293 if (found) 294 *pPpsId = ppsId; 295 done: 296 return found; 297 } 298 299 /* -------------------------------------------------------------------- 300 * Return the path to a PPS device related to tghe TT fd given. The 301 * function might even try to instantiate such a PPS device when 302 * running es effective root. Returns NULL if no PPS device can be 303 * established; otherwise it is a 'malloc()'ed area that should be 304 * 'free()'d after use. 305 */ 306 static char* 307 findMatchingPpsDev( 308 int fdtty) 309 { 310 struct stat sb; 311 dev_t ttyId, ppsId; 312 int fdpps, ldisc = N_PPS; 313 char *dpath = NULL; 314 315 /* Without the device identifier of the TTY, we're busted: */ 316 if (!getCharDevId(fdtty, &ttyId, &sb)) 317 goto done; 318 319 /* If we find a matching PPS device ID, return the path to the 320 * device. It might not open, but it's the best we can get. 321 */ 322 if (findPpsDevId(ttyId, &ppsId)) { 323 dpath = findDevByDevId(ppsId); 324 goto done; 325 } 326 327 # ifdef ENABLE_MAGICPPS 328 /* 'magic' PPS support -- try to instantiate missing PPS devices 329 * on-the-fly. Our mileage may vary -- running as root at that 330 * moment is vital for success. (We *can* create the PPS device 331 * as ordnary user, but we won't be able to open it!) 332 */ 333 334 /* If we're root, try to push the PPS LDISC to the tty FD. If 335 * that does not work out, we're busted again: 336 */ 337 if ((0 != geteuid()) || (-1 == ioctl(fdtty, TIOCSETD, &ldisc))) 338 goto done; 339 msyslog(LOG_INFO, "auto-instantiated PPS device for device %u:%u", 340 major(ttyId), minor(ttyId)); 341 342 /* We really should find a matching PPS device now. And since 343 * we're root (see above!), we should be able to open that device. 344 */ 345 if (findPpsDevId(ttyId, &ppsId)) 346 dpath = findDevByDevId(ppsId); 347 if (!dpath) 348 goto done; 349 350 /* And since we're 'root', we might as well try to clone the 351 * ownership and access rights from the original TTY to the 352 * PPS device. If that does not work, we just have to live with 353 * what we've got so far... 354 */ 355 if (-1 == (fdpps = open(dpath, OModeF))) { 356 msyslog(LOG_ERR, "could not open auto-created '%s': %m", dpath); 357 goto done; 358 } 359 if (-1 == fchmod(fdpps, sb.st_mode)) { 360 msyslog(LOG_ERR, "could not chmod auto-created '%s': %m", dpath); 361 } 362 if (-1 == fchown(fdpps, sb.st_uid, sb.st_gid)) { 363 msyslog(LOG_ERR, "could not chown auto-created '%s': %m", dpath); 364 } 365 close(fdpps); 366 # else 367 (void)ldisc; 368 # endif 369 370 done: 371 /* Whatever we go so far, that's it. */ 372 return dpath; 373 } 374 375 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 376 #endif /* linux PPS device matcher */ 377 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ 378 379 #include "ntp_clockdev.h" 380 381 int 382 ppsdev_reopen( 383 const sockaddr_u *srcadr, 384 int ttyfd , /* current tty FD, or -1 */ 385 int ppsfd , /* current pps FD, or -1 */ 386 const char *ppspath, /* path to pps device, or NULL */ 387 int omode , /* open mode for pps device */ 388 int oflags ) /* openn flags for pps device */ 389 { 390 int retfd = -1; 391 const char *altpath; 392 393 /* avoid 'unused' warnings: we might not use all args, no 394 * thanks to conditional compiling:) 395 */ 396 (void)ppspath; 397 (void)omode; 398 (void)oflags; 399 400 if (NULL != (altpath = clockdev_lookup(srcadr, 1))) 401 ppspath = altpath; 402 403 # if defined(__unix__) && !defined(_WIN32) 404 if (-1 == retfd) { 405 if (ppspath && *ppspath) { 406 retfd = open(ppspath, omode, oflags); 407 msyslog(LOG_INFO, "ppsdev_open(%s) %s", 408 ppspath, (retfd != -1 ? "succeeded" : "failed")); 409 } 410 } 411 # endif 412 413 # if defined(WITH_PPSDEV_MATCH) 414 if ((-1 == retfd) && (-1 != ttyfd)) { 415 char *xpath = findMatchingPpsDev(ttyfd); 416 if (xpath && *xpath) { 417 retfd = open(xpath, omode, oflags); 418 msyslog(LOG_INFO, "ppsdev_open(%s) %s", 419 xpath, (retfd != -1 ? "succeeded" : "failed")); 420 } 421 free(xpath); 422 } 423 # endif 424 425 /* BSDs and probably SOLARIS can use the TTY fd for the PPS API, 426 * and so does Windows where the PPS API is implemented via an 427 * IOCTL. Likewise does the 'SoftPPS' implementation in Windows 428 * based on COM Events. So, if everything else fails, simply 429 * try the FD given for the TTY/COMport... 430 */ 431 if (-1 == retfd) 432 retfd = ppsfd; 433 if (-1 == retfd) 434 retfd = ttyfd; 435 436 /* Close the old pps FD, but only if the new pps FD is neither 437 * the tty FD nor the existing pps FD! 438 */ 439 if ((retfd != ttyfd) && (retfd != ppsfd)) 440 ppsdev_close(ttyfd, ppsfd); 441 442 return retfd; 443 } 444 445 void 446 ppsdev_close( 447 int ttyfd, /* current tty FD, or -1 */ 448 int ppsfd) /* current pps FD, or -1 */ 449 { 450 /* The pps fd might be the same as the tty fd. We close the pps 451 * channel only if it's valid and _NOT_ the tty itself: 452 */ 453 if ((-1 != ppsfd) && (ttyfd != ppsfd)) 454 close(ppsfd); 455 } 456 /* --*-- that's all folks --*-- */ 457 #else 458 NONEMPTY_TRANSLATION_UNIT 459 #endif /* !defined(REFCLOCK) */ 460