1 /* @(#)lpath_unix.c 1.15 20/07/01 Copyright 2018-2020 J. Schilling */
2 #include <schily/mconfig.h>
3 #ifndef lint
4 static UConst char sccsid[] =
5 "@(#)lpath_unix.c 1.15 20/07/01 Copyright 2018-2020 J. Schilling";
6 #endif
7 /*
8 * Routines for long path names on unix like operating systems
9 *
10 * Copyright (c) 2018-2020 J. Schilling
11 */
12 /*
13 * The contents of this file are subject to the terms of the
14 * Common Development and Distribution License, Version 1.0 only
15 * (the "License"). You may not use this file except in compliance
16 * with the License.
17 *
18 * See the file CDDL.Schily.txt in this distribution for details.
19 * A copy of the CDDL is also available via the Internet at
20 * http://www.opensource.org/licenses/cddl1.txt
21 *
22 * When distributing Covered Code, include this CDDL HEADER in each
23 * file and include the License file CDDL.Schily.txt from this distribution.
24 */
25
26 #include <schily/mconfig.h>
27 #include <schily/stdio.h>
28 #include <schily/errno.h>
29 #include "star.h"
30 #include <schily/standard.h>
31 #include <schily/unistd.h>
32 #include <schily/dirent.h>
33 #include <schily/fcntl.h> /* For AT_FDCWD */
34 #include <schily/stat.h>
35 #include <schily/param.h> /* For DEV_BSIZE */
36 #include <schily/string.h>
37 #define GT_COMERR /* #define comerr gtcomerr */
38 #define GT_ERROR /* #define error gterror */
39 #include <schily/schily.h>
40 #include "starsubs.h"
41
42 #ifndef ENAMETOOLONG
43 #define ENAMETOOLONG EINVAL
44 #endif
45
46 EXPORT int lchdir __PR((char *name));
47 #if defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
48 LOCAL void sunos5_cwdfix __PR((void));
49 #endif
50 EXPORT char *lgetcwd __PR((void));
51 EXPORT int lmkdir __PR((char *name, mode_t mode));
52 EXPORT int laccess __PR((char *name, int amode));
53 EXPORT int lstatat __PR((char *name, struct stat *buf, int flag));
54 EXPORT int lchmodat __PR((char *name, mode_t mode, int flag));
55 EXPORT int lutimensat __PR((char *name, struct timespec *ts, int flag));
56 EXPORT int lreadlink __PR((char *name, char *buf, size_t bfsize));
57 EXPORT int lsymlink __PR((char *name, char *name2));
58 EXPORT int llink __PR((char *name, char *name2));
59 EXPORT int lrename __PR((char *name, char *name2));
60 EXPORT int lmknod __PR((char *name, mode_t mode, dev_t dev));
61 EXPORT int lmkfifo __PR((char *name, mode_t mode));
62 EXPORT int lchownat __PR((char *name, uid_t uid, gid_t gid, int flag));
63 EXPORT int lunlinkat __PR((char *name, int flag));
64 EXPORT char *lmktemp __PR((char *name));
65 EXPORT FILE *lfilemopen __PR((char *name, char *mode, mode_t cmode));
66 LOCAL int _fileopenat __PR((int fd, char *name, char *mode));
67 EXPORT int _lfileopen __PR((char *name, char *mode));
68 EXPORT DIR *lopendir __PR((char *name));
69 EXPORT int hop_dirs __PR((char *name, char **np));
70
71 /*
72 * A chdir() implementation that is able to deal with ENAMETOOLONG.
73 */
74 EXPORT int
lchdir(path)75 lchdir(path)
76 char *path;
77 {
78 int ret = chdir(path);
79 char *p;
80 char *p2;
81
82 if (ret >= 0 || geterrno() != ENAMETOOLONG)
83 return (ret);
84
85 p = path;
86 if (*p == '/') {
87 if ((ret = chdir("/")) < 0)
88 return (ret);
89 while (*p == '/')
90 p++;
91 }
92 while (*p) {
93 if ((p2 = strchr(p, '/')) != NULL)
94 *p2 = '\0';
95 if ((ret = chdir(p)) < 0) {
96 if (p2)
97 *p2 = '/';
98 break;
99 }
100 if (p2 == NULL)
101 break;
102 *p2++ = '/';
103 while (*p2 == '/')
104 p2++;
105 p = p2;
106 }
107 return (ret);
108 }
109
110 #if defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
111 /*
112 * Workaround a Solaris getcwd() bug that hits on newer Solaris releases with
113 * getcwd() being a system call. Once a successful getcwd() call that returns
114 * more than PATH_MAX did succeed, future calls to getcwd() always fail with
115 * ERANGE until a successfull new chdir() call has been issued.
116 */
117 LOCAL void
sunos5_cwdfix()118 sunos5_cwdfix()
119 {
120 int f = open(".", O_SEARCH|O_DIRECTORY|O_NDELAY);
121
122 if (f >= 0) {
123 chdir("/");
124 fchdir(f);
125 close(f);
126 }
127 }
128 #endif
129
130 /*
131 * A getcwd() implementation that is able to deal with more than PATH_MAX.
132 */
133 EXPORT char *
lgetcwd()134 lgetcwd()
135 {
136 char *dir = NULL;
137 char *r;
138 size_t len = PATH_MAX;
139 #define LWD_RETRY_MAX 8 /* stops at 128 * PATH_MAX */
140 int i = 0;
141
142 #if defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
143 retry:
144 #endif
145 do {
146 dir = ___realloc(dir, len, "working dir");
147 *dir = '\0';
148
149 r = getcwd(dir, len);
150 if (++i >= LWD_RETRY_MAX) /* stops at 128 * PATH_MAX */
151 break;
152 len *= 2;
153 } while (r == NULL && errno == ERANGE);
154
155 #if defined(IS_SUN) && defined(__SVR4) && defined(DO_CHDIR_LONG)
156 if (r == NULL && errno == ERANGE && i == LWD_RETRY_MAX) {
157 /*
158 * Work around a Solaris kernel bug.
159 */
160 sunos5_cwdfix();
161 goto retry;
162 }
163 #endif
164 return (r);
165 }
166
167 EXPORT int
168 #ifdef PROTOTYPES
lmkdir(char * name,mode_t mode)169 lmkdir(char *name, mode_t mode)
170 #else
171 lmkdir(name, mode)
172 char *name;
173 mode_t mode;
174 #endif
175 {
176 #ifdef HAVE_FCHDIR
177 char *p;
178 int fd;
179 int err;
180 #endif
181 int ret;
182
183 if ((ret = mkdir(name, mode)) < 0 &&
184 geterrno() != ENAMETOOLONG) {
185 return (ret);
186 }
187
188 #ifdef HAVE_FCHDIR
189 if (ret >= 0)
190 return (ret);
191
192 fd = hop_dirs(name, &p);
193 ret = mkdirat(fd, p, mode);
194 err = geterrno();
195 close(fd);
196 seterrno(err);
197 #endif
198 return (ret);
199 }
200
201 EXPORT int
laccess(name,amode)202 laccess(name, amode)
203 char *name;
204 int amode;
205 {
206 #ifdef HAVE_FCHDIR
207 char *p;
208 int fd;
209 int err;
210 #endif
211 int ret;
212
213 if ((ret = access(name, amode)) < 0 &&
214 geterrno() != ENAMETOOLONG) {
215 return (ret);
216 }
217
218 #ifdef HAVE_FCHDIR
219 if (ret >= 0)
220 return (ret);
221
222 fd = hop_dirs(name, &p);
223 ret = faccessat(fd, p, amode, 0);
224 err = geterrno();
225 close(fd);
226 seterrno(err);
227 #endif
228 return (ret);
229 }
230
231 EXPORT int
lstatat(name,buf,flag)232 lstatat(name, buf, flag)
233 char *name;
234 struct stat *buf;
235 int flag;
236 {
237 #ifdef HAVE_FCHDIR
238 char *p;
239 int fd;
240 int err;
241 #endif
242 int ret;
243
244 if ((ret = fstatat(AT_FDCWD, name, buf, flag)) < 0 &&
245 geterrno() != ENAMETOOLONG) {
246 return (ret);
247 }
248
249 #ifdef HAVE_FCHDIR
250 if (ret >= 0)
251 return (ret);
252
253 fd = hop_dirs(name, &p);
254 ret = fstatat(fd, p, buf, flag);
255 err = geterrno();
256 close(fd);
257 seterrno(err);
258 #endif
259 return (ret);
260 }
261
262
263 EXPORT int
264 #ifdef PROTOTYPES
lchmodat(char * name,mode_t mode,int flag)265 lchmodat(char *name, mode_t mode, int flag)
266 #else
267 lchmodat(name, mode, flag)
268 char *name;
269 mode_t mode;
270 int flag;
271 #endif
272 {
273 #ifdef HAVE_FCHDIR
274 char *p;
275 int fd;
276 int err;
277 #endif
278 int ret;
279
280 if ((ret = fchmodat(AT_FDCWD, name, mode, flag)) < 0 &&
281 geterrno() != ENAMETOOLONG) {
282 return (ret);
283 }
284
285 #ifdef HAVE_FCHDIR
286 if (ret >= 0)
287 return (ret);
288
289 fd = hop_dirs(name, &p);
290 ret = fchmodat(fd, p, mode, flag);
291 err = geterrno();
292 close(fd);
293 seterrno(err);
294 #endif
295 return (ret);
296 }
297
298
299 EXPORT int
lutimensat(name,ts,flag)300 lutimensat(name, ts, flag)
301 char *name;
302 struct timespec *ts;
303 int flag;
304 {
305 #ifdef HAVE_FCHDIR
306 char *p;
307 int fd;
308 int err;
309 #endif
310 int ret;
311
312 if ((ret = utimensat(AT_FDCWD, name, ts, flag)) < 0 &&
313 geterrno() != ENAMETOOLONG) {
314 return (ret);
315 }
316
317 #ifdef HAVE_FCHDIR
318 if (ret >= 0)
319 return (ret);
320
321 fd = hop_dirs(name, &p);
322 ret = utimensat(fd, p, ts, flag);
323 err = geterrno();
324 close(fd);
325 seterrno(err);
326 #endif
327 return (ret);
328 }
329
330 #ifdef HAVE_READLINK
331 EXPORT int
lreadlink(name,buf,bfsize)332 lreadlink(name, buf, bfsize)
333 char *name;
334 char *buf;
335 size_t bfsize;
336 {
337 #ifdef HAVE_FCHDIR
338 char *p;
339 int fd;
340 int err;
341 #endif
342 int ret;
343
344 if ((ret = readlink(name, buf, bfsize)) < 0 &&
345 geterrno() != ENAMETOOLONG) {
346 return (ret);
347 }
348
349 #ifdef HAVE_FCHDIR
350 if (ret >= 0)
351 return (ret);
352
353 fd = hop_dirs(name, &p);
354 ret = readlinkat(fd, p, buf, bfsize);
355 err = geterrno();
356 close(fd);
357 seterrno(err);
358 #endif
359 return (ret);
360 }
361
362 EXPORT int
lsymlink(name,name2)363 lsymlink(name, name2)
364 char *name;
365 char *name2;
366 {
367 #ifdef HAVE_FCHDIR
368 char *p;
369 int fd;
370 int err;
371 #endif
372 int ret;
373
374 if ((ret = symlink(name, name2)) < 0 &&
375 geterrno() != ENAMETOOLONG) {
376 return (ret);
377 }
378
379 #ifdef HAVE_FCHDIR
380 if (ret >= 0)
381 return (ret);
382
383 fd = hop_dirs(name2, &p);
384 ret = symlinkat(name, fd, p);
385 err = geterrno();
386 close(fd);
387 seterrno(err);
388 #endif
389 return (ret);
390 }
391 #endif
392
393 #ifdef HAVE_LINK
394 EXPORT int
llink(name,name2)395 llink(name, name2)
396 char *name;
397 char *name2;
398 {
399 #ifdef HAVE_FCHDIR
400 char *p;
401 char *p2;
402 int fd;
403 int fd2;
404 int err;
405 #endif
406 int ret;
407
408 if ((ret = link(name, name2)) < 0 &&
409 geterrno() != ENAMETOOLONG) {
410 return (ret);
411 }
412
413 #ifdef HAVE_FCHDIR
414 if (ret >= 0)
415 return (ret);
416
417 fd = hop_dirs(name, &p);
418 fd2 = hop_dirs(name2, &p2);
419 ret = linkat(fd, p, fd2, p2, 0); /* SYMLINK_FOLLOW not in emulation */
420 err = geterrno();
421 close(fd);
422 close(fd2);
423 seterrno(err);
424 #endif
425 return (ret);
426 }
427 #endif
428
429 EXPORT int
lrename(name,name2)430 lrename(name, name2)
431 char *name;
432 char *name2;
433 {
434 #ifdef HAVE_FCHDIR
435 char *p;
436 char *p2;
437 int fd;
438 int fd2;
439 int err;
440 #endif
441 int ret;
442
443 if ((ret = rename(name, name2)) < 0 &&
444 geterrno() != ENAMETOOLONG) {
445 return (ret);
446 }
447
448 #ifdef HAVE_FCHDIR
449 if (ret >= 0)
450 return (ret);
451
452 fd = hop_dirs(name, &p);
453 fd2 = hop_dirs(name2, &p2);
454 ret = renameat(fd, p, fd2, p2);
455 err = geterrno();
456 close(fd);
457 close(fd2);
458 seterrno(err);
459 #endif
460 return (ret);
461 }
462
463 #ifdef HAVE_MKNOD
464 EXPORT int
465 #ifdef PROTOTYPES
lmknod(char * name,mode_t mode,dev_t dev)466 lmknod(char *name, mode_t mode, dev_t dev)
467 #else
468 lmknod(name, mode, dev)
469 char *name;
470 mode_t mode;
471 dev_t dev;
472 #endif
473 {
474 #ifdef HAVE_FCHDIR
475 char *p;
476 int fd;
477 int err;
478 #endif
479 int ret;
480
481 if ((ret = mknod(name, mode, dev)) < 0 &&
482 geterrno() != ENAMETOOLONG) {
483 return (ret);
484 }
485
486 #ifdef HAVE_FCHDIR
487 if (ret >= 0)
488 return (ret);
489
490 fd = hop_dirs(name, &p);
491 ret = mknodat(fd, p, mode, dev);
492 err = geterrno();
493 close(fd);
494 seterrno(err);
495 #endif
496 return (ret);
497 }
498 #endif
499
500 #ifdef HAVE_MKFIFO
501 EXPORT int
502 #ifdef PROTOTYPES
lmkfifo(char * name,mode_t mode)503 lmkfifo(char *name, mode_t mode)
504 #else
505 lmkfifo(name, mode)
506 char *name;
507 mode_t mode;
508 #endif
509 {
510 #ifdef HAVE_FCHDIR
511 char *p;
512 int fd;
513 int err;
514 #endif
515 int ret;
516
517 if ((ret = mkfifo(name, mode)) < 0 &&
518 geterrno() != ENAMETOOLONG) {
519 return (ret);
520 }
521
522 #ifdef HAVE_FCHDIR
523 if (ret >= 0)
524 return (ret);
525
526 fd = hop_dirs(name, &p);
527 ret = mkfifoat(fd, p, mode);
528 err = geterrno();
529 close(fd);
530 seterrno(err);
531 #endif
532 return (ret);
533 }
534 #endif
535
536 EXPORT int
537 #ifdef PROTOTYPES
lchownat(char * name,uid_t uid,gid_t gid,int flag)538 lchownat(char *name, uid_t uid, gid_t gid, int flag)
539 #else
540 lchownat(name, uid, gid, flag)
541 char *name;
542 uid_t uid;
543 gid_t gid;
544 int flag;
545 #endif
546 {
547 #ifdef HAVE_FCHDIR
548 char *p;
549 int fd;
550 int err;
551 #endif
552 int ret;
553
554 if ((ret = fchownat(AT_FDCWD, name, uid, gid, flag)) < 0 &&
555 geterrno() != ENAMETOOLONG) {
556 return (ret);
557 }
558
559 #ifdef HAVE_FCHDIR
560 if (ret >= 0)
561 return (ret);
562
563 fd = hop_dirs(name, &p);
564 ret = fchownat(fd, p, uid, gid, flag);
565 err = geterrno();
566 close(fd);
567 seterrno(err);
568 #endif
569 return (ret);
570 }
571
572 EXPORT int
lunlinkat(name,flag)573 lunlinkat(name, flag)
574 char *name;
575 int flag;
576 {
577 #ifdef HAVE_FCHDIR
578 char *p;
579 int fd;
580 int err;
581 #endif
582 int ret;
583
584 if ((ret = unlinkat(AT_FDCWD, name, flag)) < 0 &&
585 geterrno() != ENAMETOOLONG) {
586 return (ret);
587 }
588
589 #ifdef HAVE_FCHDIR
590 if (ret >= 0)
591 return (ret);
592
593 fd = hop_dirs(name, &p);
594 ret = unlinkat(fd, p, flag);
595 err = geterrno();
596 close(fd);
597 seterrno(err);
598 #endif
599 return (ret);
600 }
601
602 EXPORT char *
lmktemp(name)603 lmktemp(name)
604 char *name;
605 {
606 #ifdef HAVE_FCHDIR
607 char *p;
608 int fd;
609 int fdh;
610 int err = 0;
611 char spath[PATH_MAX+1];
612 char *oname = spath;
613 #endif
614 char *ret;
615
616 if (name == NULL)
617 return (name);
618
619 #ifdef HAVE_FCHDIR
620 /*
621 * mktemp() is destructive, so we need to save the name before
622 * as we always fail first with long path names.
623 */
624 if (strlen(name) < PATH_MAX) {
625 strcpy(oname, name);
626 } else {
627 oname = strdup(name);
628 if (oname == NULL) {
629 name[0] = '\0';
630 return (name);
631 }
632 }
633 #endif
634 ret = mktemp(name);
635 if (ret[0] == '\0' &&
636 geterrno() != ENAMETOOLONG) {
637 #ifdef HAVE_FCHDIR
638 if (oname != spath)
639 free(oname);
640 #endif
641 return (ret);
642 }
643
644 #ifdef HAVE_FCHDIR
645 if (ret[0] != '\0') {
646 if (oname != spath)
647 free(oname);
648 return (ret);
649 }
650
651 strcpy(name, oname);
652 if (oname != spath)
653 free(oname);
654 fd = hop_dirs(name, &p);
655 if (fd >= 0) {
656 fdh = open(".", O_SEARCH|O_DIRECTORY|O_NDELAY);
657 if (fdh >= 0) {
658 fchdir(fd);
659 ret = mktemp(p);
660 err = geterrno();
661 fchdir(fdh);
662 close(fdh);
663 }
664 close(fd);
665 }
666 if (err)
667 seterrno(err);
668 #endif
669 return (ret);
670 }
671
672 EXPORT FILE *
673 #ifdef PROTOTYPES
lfilemopen(char * name,char * mode,mode_t cmode)674 lfilemopen(char *name, char *mode, mode_t cmode)
675 #else
676 lfilemopen(name, mode, cmode)
677 char *name;
678 char *mode;
679 mode_t cmode;
680 #endif
681 {
682 #ifdef HAVE_FCHDIR
683 char *p;
684 char *p2;
685 int fd;
686 int fdf;
687 int err;
688 char lmode[10];
689 #endif
690 FILE *fp;
691 int omode = 0;
692 int flag = 0;
693
694 if ((fp = filemopen(name, mode, cmode)) == NULL &&
695 geterrno() != ENAMETOOLONG) {
696 return (fp);
697 }
698
699 #ifdef HAVE_FCHDIR
700 if (fp != NULL)
701 return (fp);
702
703 fd = hop_dirs(name, &p);
704 _cvmod(mode, &omode, &flag);
705 fdf = openat(fd, p, omode, cmode);
706 p = mode;
707 p2 = lmode;
708 while (*p) {
709 if (*p == 'c' || *p == 't' || *p == 'e') {
710 p++;
711 continue;
712 }
713 *p2++ = *p++;
714 }
715 *p2 = '\0';
716 if (fdf >= 0 && (fp = fileluopen(fdf, lmode)) == NULL)
717 close(fdf);
718 err = geterrno();
719 close(fd);
720 seterrno(err);
721 #endif
722 return (fp);
723 }
724
725 LOCAL int
_fileopenat(fd,name,smode)726 _fileopenat(fd, name, smode)
727 int fd;
728 char *name;
729 char *smode;
730 {
731 int ret;
732 int omode = 0;
733 int flag = 0;
734
735 if (!_cvmod(smode, &omode, &flag))
736 return (-1);
737
738 retry:
739 if ((ret = openat(fd, name, omode, S_IRWALL)) < 0) {
740 if (geterrno() == EINTR)
741 goto retry;
742 return (-1);
743 }
744
745 return (ret);
746 }
747
748 EXPORT int
_lfileopen(name,smode)749 _lfileopen(name, smode)
750 char *name;
751 char *smode;
752 {
753 #ifdef HAVE_FCHDIR
754 char *p;
755 int fd;
756 int err;
757 #endif
758 int fdf;
759
760 if ((fdf = _fileopenat(AT_FDCWD, name, smode)) < 0 &&
761 geterrno() != ENAMETOOLONG) {
762 return (fdf);
763 }
764
765 #ifdef HAVE_FCHDIR
766 if (fdf >= 0)
767 return (fdf);
768
769 fd = hop_dirs(name, &p);
770 fdf = _fileopenat(fd, p, smode);
771 err = geterrno();
772 close(fd);
773 seterrno(err);
774 #endif
775 return (fdf);
776 }
777
778 EXPORT DIR *
lopendir(name)779 lopendir(name)
780 char *name;
781 {
782 #ifdef HAVE_FCHDIR
783 char *p;
784 int fd;
785 int dfd;
786 #endif
787 DIR *ret = NULL;
788
789 if ((ret = opendir(name)) == NULL && geterrno() != ENAMETOOLONG)
790 return ((DIR *)NULL);
791
792 #ifdef HAVE_FCHDIR
793 if (ret)
794 return (ret);
795
796 fd = hop_dirs(name, &p);
797 if ((dfd = openat(fd, p, O_RDONLY|O_DIRECTORY|O_NDELAY)) < 0) {
798 close(fd);
799 return ((DIR *)NULL);
800 }
801 close(fd);
802 ret = fdopendir(dfd);
803 #endif
804 return (ret);
805 }
806
807 #ifdef HAVE_FCHDIR
808 EXPORT int
hop_dirs(name,np)809 hop_dirs(name, np)
810 char *name;
811 char **np;
812 {
813 char *p;
814 char *p2;
815 int fd;
816 int dfd;
817 int err;
818
819 p = name;
820 fd = AT_FDCWD;
821 if (*p == '/') {
822 fd = openat(fd, "/", O_SEARCH|O_DIRECTORY|O_NDELAY);
823 while (*p == '/')
824 p++;
825 }
826 while (*p) {
827 if ((p2 = strchr(p, '/')) != NULL) {
828 if (p2[1] == '\0')
829 break;
830 *p2 = '\0';
831 } else {
832 break; /* No slash remains, return the prefix fd */
833 }
834 if ((dfd = openat(fd, p, O_SEARCH|O_DIRECTORY|O_NDELAY)) < 0) {
835 err = geterrno();
836
837 close(fd);
838 if (err == EMFILE)
839 seterrno(err);
840 else
841 seterrno(ENAMETOOLONG);
842 *p2 = '/';
843 return (dfd);
844 }
845 close(fd); /* Don't care about AT_FDCWD, it is negative */
846 fd = dfd;
847 *p2++ = '/'; /* p2 is always != NULL here */
848 while (*p2 == '/')
849 p2++;
850 p = p2;
851 }
852 *np = p;
853 return (fd);
854 }
855 #endif
856