2 * ntp_ppsdev.c - PPS-device support
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.
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.
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 * ---------------------------------------------------------------------
31 #if defined(HAVE_UNISTD_H)
34 #if defined(HAVE_FCNTL_H)
40 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
41 #if defined(__linux__) && defined(HAVE_OPENAT) && defined(HAVE_FDOPENDIR)
42 #define WITH_PPSDEV_MATCH
43 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
50 #include <sys/ioctl.h>
51 #include <sys/types.h>
53 #include <sys/sysmacros.h>
54 #include <linux/tty.h>
64 static const int OModeF = O_CLOEXEC|O_RDONLY|O_NOCTTY;
65 static const int OModeD = O_CLOEXEC|O_RDONLY|O_DIRECTORY;
67 /* ------------------------------------------------------------------ */
68 /* extended directory stream
71 int dfd; /* file descriptor for dir for 'openat()' */
72 DIR *dir; /* directory stream for iteration */
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 */
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
100 if (-1 == (pxdir->dfd = openat(fdo, path, OModeD)))
102 if (NULL == (pxdir->dir = fdopendir(pxdir->dfd)))
111 /* --------------------------------------------------------------------
112 * read content of a file (with a size limit) into a piece of allocated
113 * memory and trim any trailing whitespace.
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...)
130 if (-1 == (dfd = openat(rfd, path, OModeF)) || -1 == fstat(dfd, &sb))
132 if ((sb.st_size > 0x2000) || (NULL == (ret = malloc(sb.st_size + 1))))
134 if (1 > (rdlen = read(dfd, ret, sb.st_size)))
138 while (rdlen > 0 && ret[rdlen - 1] <= ' ')
150 /* --------------------------------------------------------------------
151 * Scan the "/dev" directory for a device with a given major and minor
152 * device id. Return the path if found.
163 if (!xdirOpenAt(&xdir, AT_FDCWD, "/dev"))
166 while (!name && (dent = readdir(xdir.dir))) {
167 if (-1 == fstatat(xdir.dfd, dent->d_name,
168 &sb, AT_SYMLINK_NOFOLLOW))
170 if (!S_ISCHR(sb.st_mode))
172 if (sb.st_rdev == rdev) {
173 if (-1 == asprintf(&name, "/dev/%s", dent->d_name))
183 /* --------------------------------------------------------------------
184 * Get the mofor:minor device id for a character device file descriptor
197 if (-1 != fstat(fd, psb)) {
198 rc = S_ISCHR(psb->st_mode);
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.
218 unsigned long dmaj, dmin;
220 char *bufp, *endp, *scan;
222 /* 'path' contains the primary path to the associated TTY:
223 * we 'stat()' for the device id in 'st_rdev'.
225 if (NULL == (bufp = readFileAt(fdDir, "path")))
227 if ((-1 == stat(bufp, &sb)) || !S_ISCHR(sb.st_mode))
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....
236 if (NULL == (bufp = readFileAt(fdDir, "dev")))
238 dmaj = strtoul((scan = bufp), &endp, 10);
239 if ((endp == scan) || (*endp != ':') || (dmaj >= 256))
241 dmin = strtoul((scan = endp + 1), &endp, 10);
242 if ((endp == scan) || (*endp >= ' ') || (dmin >= 256))
244 *pPps = makedev((unsigned int)dmaj, (unsigned int)dmin);
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...
269 if (!xdirOpenAt(&ClassDir, AT_FDCWD, "/sys/class/pps"))
272 while (!found && (dent = readdir(ClassDir.dir))) {
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:
277 if (strncmp("pps", dent->d_name, 3))
279 fdDevDir = openat(ClassDir.dfd, dent->d_name, OModeD);
283 /* get the data and check if device ID for the TTY
284 * is what we're looking for:
286 found = getPpsTuple(fdDevDir, &othId, &ppsId)
291 xdirClose(&ClassDir);
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.
312 int fdpps, ldisc = N_PPS;
315 /* Without the device identifier of the TTY, we're busted: */
316 if (!getCharDevId(fdtty, &ttyId, &sb))
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.
322 if (findPpsDevId(ttyId, &ppsId)) {
323 dpath = findDevByDevId(ppsId);
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!)
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:
337 if ((0 != geteuid()) || (-1 == ioctl(fdtty, TIOCSETD, &ldisc)))
339 msyslog(LOG_INFO, "auto-instantiated PPS device for device %u:%u",
340 major(ttyId), minor(ttyId));
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.
345 if (findPpsDevId(ttyId, &ppsId))
346 dpath = findDevByDevId(ppsId);
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...
355 if (-1 == (fdpps = open(dpath, OModeF))) {
356 msyslog(LOG_ERR, "could not open auto-created '%s': %m", dpath);
359 if (-1 == fchmod(fdpps, sb.st_mode)) {
360 msyslog(LOG_ERR, "could not chmod auto-created '%s': %m", dpath);
362 if (-1 == fchown(fdpps, sb.st_uid, sb.st_gid)) {
363 msyslog(LOG_ERR, "could not chown auto-created '%s': %m", dpath);
371 /* Whatever we go so far, that's it. */
375 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
376 #endif /* linux PPS device matcher */
377 /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
379 #include "ntp_clockdev.h"
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 */
393 /* avoid 'unused' warnings: we might not use all args, no
394 * thanks to conditional compiling:)
400 if (NULL != (altpath = clockdev_lookup(srcadr, 1)))
403 # if defined(__unix__) && !defined(_WIN32)
405 if (ppspath && *ppspath) {
406 retfd = open(ppspath, omode, oflags);
407 msyslog(LOG_INFO, "ppsdev_open(%s) %s",
408 ppspath, (retfd != -1 ? "succeeded" : "failed"));
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"));
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...
436 /* Close the old pps FD, but only if the new pps FD is neither
437 * the tty FD nor the existing pps FD!
439 if ((retfd != ttyfd) && (retfd != ppsfd))
440 ppsdev_close(ttyfd, ppsfd);
447 int ttyfd, /* current tty FD, or -1 */
448 int ppsfd) /* current pps FD, or -1 */
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:
453 if ((-1 != ppsfd) && (ttyfd != ppsfd))
456 /* --*-- that's all folks --*-- */
458 NONEMPTY_TRANSLATION_UNIT
459 #endif /* !defined(REFCLOCK) */