xref: /freebsd/contrib/ntp/ntpd/ntp_ppsdev.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
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