1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <sys/fstyp.h>
  28 #include <sys/fsid.h>
  29 
  30 #include <errno.h>
  31 #include <unistd.h>
  32 #include <stdio.h>
  33 #include <sys/types.h>
  34 #include <sys/stat.h>
  35 #include <sys/vnode.h>
  36 #include <fcntl.h>
  37 #include <string.h>
  38 #include <utime.h>
  39 #include <atomic.h>
  40 
  41 #include <sys/lx_syscall.h>
  42 #include <sys/lx_types.h>
  43 #include <sys/lx_debug.h>
  44 #include <sys/lx_misc.h>
  45 #include <sys/lx_fcntl.h>
  46 
  47 static int
  48 install_checkpath(uintptr_t p1)
  49 {
  50         int saved_errno = errno;
  51         char path[MAXPATHLEN];
  52 
  53         /*
  54          * The "dev" RPM package wants to modify /dev/pts, but /dev/pts is a
  55          * lofs mounted copy of /native/dev/pts, so that won't work.
  56          *
  57          * Instead, if we're trying to modify /dev/pts from install mode, just
  58          * act as if it succeded.
  59          */
  60         if (uucopystr((void *)p1, path, MAXPATHLEN) == -1)
  61                 return (-errno);
  62 
  63         if (strcmp(path, "/dev/pts") == 0)
  64                 return (0);
  65 
  66         errno = saved_errno;
  67         return (-errno);
  68 }
  69 
  70 /*
  71  * Convert linux LX_AT_* flags to solaris AT_* flags, while verifying allowed
  72  * flags have been passed. This also allows EACCESS/REMOVEDIR to be translated
  73  * correctly since on linux they have the same value.
  74  */
  75 int
  76 ltos_at_flag(int lflag, int allow)
  77 {
  78         int sflag = 0;
  79 
  80         if ((lflag & LX_AT_EACCESS) && (allow & AT_EACCESS)) {
  81                 lflag &= ~LX_AT_EACCESS;
  82                 sflag |= AT_EACCESS;
  83         }
  84 
  85         if ((lflag & LX_AT_REMOVEDIR) && (allow & AT_REMOVEDIR)) {
  86                 lflag &= ~LX_AT_REMOVEDIR;
  87                 sflag |= AT_REMOVEDIR;
  88         }
  89 
  90         if ((lflag & LX_AT_SYMLINK_NOFOLLOW) && (allow & AT_SYMLINK_NOFOLLOW)) {
  91                 lflag &= ~LX_AT_SYMLINK_NOFOLLOW;
  92                 sflag |= AT_SYMLINK_NOFOLLOW;
  93         }
  94 
  95         /* right now solaris doesn't have a _FOLLOW flag, so use a fake one */
  96         if ((lflag & LX_AT_SYMLINK_FOLLOW) && (allow & LX_AT_SYMLINK_FOLLOW)) {
  97                 lflag &= ~LX_AT_SYMLINK_FOLLOW;
  98                 sflag |= LX_AT_SYMLINK_FOLLOW;
  99         }
 100 
 101         /* if flag is not zero than some flags did not hit the above code */
 102         if (lflag)
 103                 return (-EINVAL);
 104 
 105         return (sflag);
 106 }
 107 
 108 
 109 /*
 110  * Miscellaneous file-related system calls.
 111  */
 112 
 113 /*
 114  * Linux creates half-duplex unnamed pipes and Solaris creates full-duplex
 115  * pipes.  Thus, to get the correct semantics, our simple pipe() system
 116  * call actually needs to create a named pipe, do three opens, a close, and
 117  * an unlink.  This is woefully expensive.  If performance becomes a real
 118  * issue, we can implement a half-duplex pipe() in the brand module.
 119  */
 120 #define PIPENAMESZ      32 /* enough room for /tmp/.pipe.<pid>.<num> */
 121 
 122 int
 123 lx_pipe(uintptr_t p1)
 124 {
 125         static uint32_t pipecnt = 0;
 126         int cnt;
 127         char pipename[PIPENAMESZ];
 128         int fds[3];
 129         int r = 0;
 130 
 131         fds[0] = -1;
 132         fds[1] = -1;
 133         fds[2] = -1;
 134 
 135         /*
 136          * Construct a name for the named pipe: /tmp/.pipe.<pid>.<++cnt>
 137          */
 138         cnt = atomic_inc_32_nv(&pipecnt);
 139 
 140         (void) snprintf(pipename, PIPENAMESZ, "/tmp/.pipe.%d.%d",
 141             getpid(), cnt);
 142 
 143         if (mkfifo(pipename, 0600))
 144                 return (-errno);
 145 
 146         /*
 147          * To prevent either the read-only or write-only open from
 148          * blocking, we first need to open the pipe for both reading and
 149          * writing.
 150          */
 151         if (((fds[2] = open(pipename, O_RDWR)) < 0) ||
 152             ((fds[0] = open(pipename, O_RDONLY)) < 0) ||
 153             ((fds[1] = open(pipename, O_WRONLY)) < 0)) {
 154                 r = errno;
 155         } else {
 156                 /*
 157                  * Copy the two one-way fds back to the app's address
 158                  * space.
 159                  */
 160                 if (uucopy(fds, (void *)p1, 2 * sizeof (int)))
 161                         r = errno;
 162         }
 163 
 164         if (fds[2] >= 0)
 165                 (void) close(fds[2]);
 166         (void) unlink(pipename);
 167 
 168         if (r != 0) {
 169                 if (fds[0] >= 0)
 170                         (void) close(fds[0]);
 171                 if (fds[1] >= 0)
 172                         (void) close(fds[1]);
 173         }
 174 
 175         return (-r);
 176 }
 177 
 178 /*
 179  * On Linux, even root cannot create a link to a directory, so we have to
 180  * add an explicit check.
 181  */
 182 int
 183 lx_link(uintptr_t p1, uintptr_t p2)
 184 {
 185         char *from = (char *)p1;
 186         char *to = (char *)p2;
 187         struct stat64 statbuf;
 188 
 189         if ((stat64(from, &statbuf) == 0) && S_ISDIR(statbuf.st_mode))
 190                 return (-EPERM);
 191 
 192         return (link(from, to) ? -errno : 0);
 193 }
 194 
 195 /*
 196  * On Linux, an unlink of a directory returns EISDIR, not EPERM.
 197  */
 198 int
 199 lx_unlink(uintptr_t p)
 200 {
 201         char *pathname = (char *)p;
 202         struct stat64 statbuf;
 203 
 204         if ((lstat64(pathname, &statbuf) == 0) && S_ISDIR(statbuf.st_mode))
 205                 return (-EISDIR);
 206 
 207         return (unlink(pathname) ? -errno : 0);
 208 }
 209 
 210 int
 211 lx_unlinkat(uintptr_t ext1, uintptr_t p1, uintptr_t p2)
 212 {
 213         int atfd = (int)ext1;
 214         char *pathname = (char *)p1;
 215         int flag = (int)p2;
 216         struct stat64 statbuf;
 217 
 218         if (atfd == LX_AT_FDCWD)
 219                 atfd = AT_FDCWD;
 220 
 221         flag = ltos_at_flag(flag, AT_REMOVEDIR);
 222         if (flag < 0)
 223                 return (-EINVAL);
 224 
 225         if (!(flag & AT_REMOVEDIR)) {
 226                 /* Behave like unlink() */
 227                 if ((fstatat64(atfd, pathname, &statbuf, AT_SYMLINK_NOFOLLOW) ==
 228                     0) && S_ISDIR(statbuf.st_mode))
 229                         return (-EISDIR);
 230         }
 231 
 232         return (unlinkat(atfd, pathname, flag) ? -errno : 0);
 233 }
 234 
 235 /*
 236  * fsync() and fdatasync() - On Solaris, these calls translate into a common
 237  * fsync() syscall with a different parameter, so we layer on top of the librt
 238  * functions instead.
 239  */
 240 int
 241 lx_fsync(uintptr_t fd)
 242 {
 243         int fildes = (int)fd;
 244         struct stat64 statbuf;
 245 
 246         if ((fstat64(fildes, &statbuf) == 0) &&
 247             (S_ISCHR(statbuf.st_mode) || S_ISFIFO(statbuf.st_mode)))
 248                 return (-EINVAL);
 249 
 250         return (fsync((int)fd) ? -errno : 0);
 251 }
 252 
 253 int
 254 lx_fdatasync(uintptr_t fd)
 255 {
 256         int fildes = (int)fd;
 257         struct stat64 statbuf;
 258 
 259         if ((fstat64(fildes, &statbuf) == 0) &&
 260             (S_ISCHR(statbuf.st_mode) || S_ISFIFO(statbuf.st_mode)))
 261                 return (-EINVAL);
 262 
 263         return (fdatasync((int)fd) ? -errno : 0);
 264 }
 265 
 266 /*
 267  * Linux, unlike Solaris, ALWAYS resets the setuid and setgid bits on a
 268  * chown/fchown  regardless of whether it was done by root or not.  Therefore,
 269  * we must do extra work after each chown/fchown call to emulate this behavior.
 270  */
 271 #define SETUGID (S_ISUID | S_ISGID)
 272 
 273 /*
 274  * [lf]chown16() - Translate the uid/gid and pass onto the real functions.
 275  */
 276 int
 277 lx_chown16(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 278 {
 279         char *filename = (char *)p1;
 280         struct stat64 statbuf;
 281 
 282         if (chown(filename, LX_UID16_TO_UID32((lx_gid16_t)p2),
 283             LX_GID16_TO_GID32((lx_gid16_t)p3)))
 284                 return (-errno);
 285 
 286         if (stat64(filename, &statbuf) == 0) {
 287                 statbuf.st_mode &= ~S_ISUID;
 288                 if (statbuf.st_mode & S_IXGRP)
 289                         statbuf.st_mode &= ~S_ISGID;
 290                 (void) chmod(filename, (statbuf.st_mode & MODEMASK));
 291         }
 292 
 293         return (0);
 294 }
 295 
 296 int
 297 lx_fchown16(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 298 {
 299         int fd = (int)p1;
 300         struct stat64 statbuf;
 301 
 302         if (fchown(fd, LX_UID16_TO_UID32((lx_gid16_t)p2),
 303             LX_GID16_TO_GID32((lx_gid16_t)p3)))
 304                 return (-errno);
 305 
 306         if (fstat64(fd, &statbuf) == 0) {
 307                 statbuf.st_mode &= ~S_ISUID;
 308                 if (statbuf.st_mode & S_IXGRP)
 309                         statbuf.st_mode &= ~S_ISGID;
 310                 (void) fchmod(fd, (statbuf.st_mode & MODEMASK));
 311         }
 312 
 313         return (0);
 314 }
 315 
 316 int
 317 lx_lchown16(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 318 {
 319         return (lchown((char *)p1, LX_UID16_TO_UID32((lx_gid16_t)p2),
 320             LX_GID16_TO_GID32((lx_gid16_t)p3)) ? -errno : 0);
 321 }
 322 
 323 int
 324 lx_chown(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 325 {
 326         char *filename = (char *)p1;
 327         struct stat64 statbuf;
 328         int ret;
 329 
 330         ret = chown(filename, (uid_t)p2, (gid_t)p3);
 331 
 332         if (ret < 0) {
 333                 /*
 334                  * If chown() failed and we're in install mode, return success
 335                  * if the the reason we failed was because the source file
 336                  * didn't actually exist or if we're trying to modify /dev/pts.
 337                  */
 338                 if ((lx_install != 0) &&
 339                     ((errno == ENOENT) || (install_checkpath(p1) == 0)))
 340                         return (0);
 341 
 342                 return (-errno);
 343         }
 344 
 345         if (stat64(filename, &statbuf) == 0) {
 346                 statbuf.st_mode &= ~S_ISUID;
 347                 if (statbuf.st_mode & S_IXGRP)
 348                         statbuf.st_mode &= ~S_ISGID;
 349                 (void) chmod(filename, (statbuf.st_mode & MODEMASK));
 350         }
 351 
 352         return (0);
 353 }
 354 
 355 int
 356 lx_fchown(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 357 {
 358         int fd = (int)p1;
 359         struct stat64 statbuf;
 360 
 361         if (fchown(fd, (uid_t)p2, (gid_t)p3))
 362                 return (-errno);
 363 
 364         if (fstat64(fd, &statbuf) == 0) {
 365                 statbuf.st_mode &= ~S_ISUID;
 366                 if (statbuf.st_mode & S_IXGRP)
 367                         statbuf.st_mode &= ~S_ISGID;
 368                 (void) fchmod(fd, (statbuf.st_mode & MODEMASK));
 369         }
 370 
 371         return (0);
 372 }
 373 
 374 int
 375 lx_chmod(uintptr_t p1, uintptr_t p2)
 376 {
 377         int ret;
 378 
 379         ret = chmod((const char *)p1, (mode_t)p2);
 380 
 381         if (ret < 0) {
 382                 /*
 383                  * If chown() failed and we're in install mode, return success
 384                  * if the the reason we failed was because the source file
 385                  * didn't actually exist or if we're trying to modify /dev/pts.
 386                  */
 387                 if ((lx_install != 0) &&
 388                     ((errno == ENOENT) || (install_checkpath(p1) == 0)))
 389                         return (0);
 390 
 391                 return (-errno);
 392         }
 393 
 394         return (0);
 395 }
 396 
 397 int
 398 lx_utime(uintptr_t p1, uintptr_t p2)
 399 {
 400         int ret;
 401 
 402         ret = utime((const char *)p1, (const struct utimbuf *)p2);
 403 
 404         if (ret < 0) {
 405                 /*
 406                  * If chown() failed and we're in install mode, return success
 407                  * if the the reason we failed was because the source file
 408                  * didn't actually exist or if we're trying to modify /dev/pts.
 409                  */
 410                 if ((lx_install != 0) &&
 411                     ((errno == ENOENT) || (install_checkpath(p1) == 0)))
 412                         return (0);
 413 
 414                 return (-errno);
 415         }
 416 
 417         return (0);
 418 }
 419 
 420 /*
 421  * llseek() - The Linux implementation takes an additional parameter, which is
 422  * the resulting position in the file.
 423  */
 424 int
 425 lx_llseek(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4,
 426     uintptr_t p5)
 427 {
 428         offset_t ret;
 429         offset_t *res = (offset_t *)p4;
 430 
 431         /* SEEK_DATA and SEEK_HOLE are only valid in Solaris */
 432         if ((int)p5 > SEEK_END)
 433                 return (-EINVAL);
 434 
 435         if ((ret = llseek((int)p1, LX_32TO64(p3, p2), p5)) < 0)
 436                 return (-errno);
 437 
 438         *res = ret;
 439         return (0);
 440 }
 441 
 442 /*
 443  * seek() - When the resultant file offset cannot be represented in 32 bits,
 444  * Linux performs the seek but Solaris doesn't, though both set EOVERFLOW.  We
 445  * call llseek() and then check to see if we need to return EOVERFLOW.
 446  */
 447 int
 448 lx_lseek(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 449 {
 450         offset_t offset = (offset_t)(off_t)(p2);        /* sign extend */
 451         offset_t ret;
 452         off_t ret32;
 453 
 454         /* SEEK_DATA and SEEK_HOLE are only valid in Solaris */
 455         if ((int)p3 > SEEK_END)
 456                 return (-EINVAL);
 457 
 458         if ((ret = llseek((int)p1, offset, p3)) < 0)
 459                 return (-errno);
 460 
 461         ret32 = (off_t)ret;
 462         if ((offset_t)ret32 == ret)
 463                 return (ret32);
 464         else
 465                 return (-EOVERFLOW);
 466 }
 467 
 468 /*
 469  * Neither Solaris nor Linux actually returns anything to the caller, but glibc
 470  * expects to see SOME value returned, so placate it and return 0.
 471  */
 472 int
 473 lx_sync(void)
 474 {
 475         sync();
 476         return (0);
 477 }
 478 
 479 int
 480 lx_rmdir(uintptr_t p1)
 481 {
 482         int r;
 483 
 484         r = rmdir((char *)p1);
 485         if (r < 0)
 486                 return ((errno == EEXIST) ? -ENOTEMPTY : -errno);
 487         return (0);
 488 }
 489 
 490 /*
 491  * Exactly the same as Solaris' sysfs(2), except Linux numbers their fs indices
 492  * starting at 0, and Solaris starts at 1.
 493  */
 494 int
 495 lx_sysfs(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 496 {
 497         int option = (int)p1;
 498         int res;
 499 
 500         /*
 501          * Linux actually doesn't have #defines for these; their sysfs(2)
 502          * man page literally defines the "option" field as being 1, 2 or 3,
 503          * corresponding to Solaris' GETFSIND, GETFSTYP and GETNFSTYP,
 504          * respectively.
 505          */
 506         switch (option) {
 507                 case 1:
 508                         if ((res = sysfs(GETFSIND, (const char *)p2)) < 0)
 509                                 return (-errno);
 510 
 511                         return (res - 1);
 512 
 513                 case 2:
 514                         if ((res = sysfs(GETFSTYP, (int)p2 + 1,
 515                             (char *)p3)) < 0)
 516                                 return (-errno);
 517 
 518                         return (0);
 519 
 520                 case 3:
 521                         if ((res = sysfs(GETNFSTYP)) < 0)
 522                                 return (-errno);
 523 
 524                         return (res);
 525 
 526                 default:
 527                         break;
 528         }
 529 
 530         return (-EINVAL);
 531 }
 532 
 533 int
 534 lx_faccessat(uintptr_t p1, uintptr_t p2, uintptr_t p3, uintptr_t p4)
 535 {
 536         int atfd = (int)p1;
 537         char *path = (char *)p2;
 538         int mode = (mode_t)p3;
 539         int flag = (int)p4;
 540 
 541         if (atfd == LX_AT_FDCWD)
 542                 atfd = AT_FDCWD;
 543 
 544         flag = ltos_at_flag(flag, AT_EACCESS);
 545         if (flag < 0)
 546                 return (-EINVAL);
 547 
 548         return (faccessat(atfd, path, mode, flag) ? -errno : 0);
 549 }
 550 
 551 int
 552 lx_futimesat(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 553 {
 554         int atfd = (int)p1;
 555         char *path = (char *)p2;
 556         struct timeval *times = (struct timeval *)p3;
 557 
 558         if (atfd == LX_AT_FDCWD)
 559                 atfd = AT_FDCWD;
 560 
 561         return (futimesat(atfd, path, times) ? -errno : 0);
 562 }
 563 
 564 
 565 /*
 566  * Constructs an absolute path string in buf from the path of fd and the
 567  * relative path string pointed to by "p1". This is required for emulating
 568  * *at() system calls.
 569  * Example:
 570  *    If the path of fd is "/foo/bar" and path is "etc" the string returned is
 571  *    "/foo/bar/etc", if the fd is a file fd then it fails with ENOTDIR.
 572  *    If path is absolute then no modifcations are made to it when copied.
 573  */
 574 static int
 575 getpathat(int fd, uintptr_t p1, char *outbuf, size_t outbuf_size)
 576 {
 577         char pathbuf[MAXPATHLEN];
 578         char fdpathbuf[MAXPATHLEN];
 579         char *fdpath;
 580         struct stat64 statbuf;
 581 
 582         if (uucopystr((void *)p1, pathbuf, MAXPATHLEN) == -1)
 583                 return (-errno);
 584 
 585         /* If the path is absolute then we can early out */
 586         if ((pathbuf[0] == '/') || (fd == LX_AT_FDCWD)) {
 587                 (void) strlcpy(outbuf, pathbuf, outbuf_size);
 588                 return (0);
 589         }
 590 
 591         fdpath = lx_fd_to_path(fd, fdpathbuf, sizeof (fdpathbuf));
 592         if (fdpath == NULL)
 593                 return (-EBADF);
 594 
 595         if ((fstat64(fd, &statbuf) < 0))
 596                 return (-EBADF);
 597 
 598         if (!S_ISDIR(statbuf.st_mode))
 599                 return (-ENOTDIR);
 600 
 601         if (snprintf(outbuf, outbuf_size, "%s/%s", fdpath, pathbuf) >
 602             (outbuf_size-1))
 603                 return (-ENAMETOOLONG);
 604 
 605         return (0);
 606 }
 607 
 608 int
 609 lx_mkdirat(uintptr_t p1, uintptr_t p2, uintptr_t p3)
 610 {
 611         int atfd = (int)p1;
 612         mode_t mode = (mode_t)p3;
 613         char pathbuf[MAXPATHLEN];
 614         int ret;
 615 
 616         ret = getpathat(atfd, p2, pathbuf, sizeof (pathbuf));
 617         if (ret < 0)
 618                 return (ret);
 619 
 620         return (mkdir(pathbuf, mode) ? -errno : 0);
 621 }
 622 
 623 int
 624 lx_mknodat(uintptr_t ext1, uintptr_t p1, uintptr_t p2, uintptr_t p3)
 625 {
 626         int atfd = (int)ext1;
 627         char pathbuf[MAXPATHLEN];
 628         int ret;
 629 
 630         ret = getpathat(atfd, p1, pathbuf, sizeof (pathbuf));
 631         if (ret < 0)
 632                 return (ret);
 633 
 634         return (lx_mknod((uintptr_t)pathbuf, p2, p3));
 635 }
 636 
 637 int
 638 lx_symlinkat(uintptr_t p1, uintptr_t ext1, uintptr_t p2)
 639 {
 640         int atfd = (int)ext1;
 641         char pathbuf[MAXPATHLEN];
 642         int ret;
 643 
 644         ret = getpathat(atfd, p2, pathbuf, sizeof (pathbuf));
 645         if (ret < 0)
 646                 return (ret);
 647 
 648         return (symlink((char *)p1, pathbuf) ? -errno : 0);
 649 }
 650 
 651 int
 652 lx_linkat(uintptr_t ext1, uintptr_t p1, uintptr_t ext2, uintptr_t p2,
 653     uintptr_t p3)
 654 {
 655         int atfd1 = (int)ext1;
 656         int atfd2 = (int)ext2;
 657         char pathbuf1[MAXPATHLEN];
 658         char pathbuf2[MAXPATHLEN];
 659         int ret;
 660 
 661         /*
 662          * The flag specifies whether the hardlink will point to a symlink or
 663          * not, on solaris the default behaviour of link() is to dereference a
 664          * symlink and there is no obvious way to trigger the other behaviour.
 665          * So for now we just ignore this flag and act like link().
 666          */
 667         /* LINTED [set but not used in function] */
 668         int flag = p3;
 669 
 670         if (flag != p3)
 671                 return (flag); // workaround.
 672 
 673         ret = getpathat(atfd1, p1, pathbuf1, sizeof (pathbuf1));
 674         if (ret < 0)
 675                 return (ret);
 676 
 677         ret = getpathat(atfd2, p2, pathbuf2, sizeof (pathbuf2));
 678         if (ret < 0)
 679                 return (ret);
 680 
 681         return (lx_link((uintptr_t)pathbuf1, (uintptr_t)pathbuf2));
 682 }
 683 
 684 int
 685 lx_readlinkat(uintptr_t ext1, uintptr_t p1, uintptr_t p2, uintptr_t p3)
 686 {
 687         int atfd = (int)ext1;
 688         char pathbuf[MAXPATHLEN];
 689         int ret;
 690 
 691         ret = getpathat(atfd, p1, pathbuf, sizeof (pathbuf));
 692         if (ret < 0)
 693                 return (ret);
 694 
 695         ret = readlink(pathbuf, (char *)p2, (size_t)p3);
 696         if (ret < 0)
 697                 return (-errno);
 698 
 699         return (ret);
 700 }
 701 
 702 int
 703 lx_fchownat(uintptr_t ext1, uintptr_t p1, uintptr_t p2, uintptr_t p3,
 704     uintptr_t p4)
 705 {
 706         int flag;
 707         int atfd = (int)ext1;
 708         char pathbuf[MAXPATHLEN];
 709         int ret;
 710 
 711         flag = ltos_at_flag(p4, AT_SYMLINK_NOFOLLOW);
 712         if (flag < 0)
 713                 return (-EINVAL);
 714 
 715         ret = getpathat(atfd, p1, pathbuf, sizeof (pathbuf));
 716         if (ret < 0)
 717                 return (ret);
 718 
 719         if (flag & AT_SYMLINK_NOFOLLOW)
 720                 return (lchown(pathbuf, (uid_t)p2, (gid_t)p3) ? -errno : 0);
 721         else
 722                 return (lx_chown((uintptr_t)pathbuf, p2, p3));
 723 }
 724 
 725 int
 726 lx_fchmodat(uintptr_t ext1, uintptr_t p1, uintptr_t p2, uintptr_t p3)
 727 {
 728         int atfd = (int)ext1;
 729         char pathbuf[MAXPATHLEN];
 730         int ret;
 731 
 732         /*
 733          * It seems that at least some versions of glibc do not set or clear
 734          * the flags arg, so checking them will result in random behaviour.
 735          */
 736         /* LINTED [set but not used in function] */
 737         int flag = p3;
 738 
 739         if (flag != p3)
 740                 return (flag); // workaround.
 741 
 742         ret = getpathat(atfd, p1, pathbuf, sizeof (pathbuf));
 743         if (ret < 0)
 744                 return (ret);
 745 
 746         return (lx_chmod((uintptr_t)pathbuf, p2));
 747 }