safefile.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sendmail.h>
#include <sm/io.h>
#include <sm/errstring.h>
SM_RCSID("@(#)$Id: safefile.c,v 8.128 2004/09/30 18:15:49 ca Exp $")
/*
** SAFEFILE -- return 0 if a file exists and is safe for a user.
**
** Parameters:
** fn -- filename to check.
** uid -- user id to compare against.
** gid -- group id to compare against.
** user -- user name to compare against (used for group
** sets).
** flags -- modifiers:
** SFF_MUSTOWN -- "uid" must own this file.
** SFF_NOSLINK -- file cannot be a symbolic link.
** mode -- mode bits that must match.
** st -- if set, points to a stat structure that will
** get the stat info for the file.
**
** Returns:
** 0 if fn exists, is owned by uid, and matches mode.
** An errno otherwise. The actual errno is cleared.
**
** Side Effects:
** none.
*/
int
safefile(fn, uid, gid, user, flags, mode, st)
char *fn;
UID_T uid;
GID_T gid;
char *user;
long flags;
int mode;
struct stat *st;
{
register char *p;
register struct group *gr = NULL;
int file_errno = 0;
bool checkpath;
struct stat stbuf;
struct stat fstbuf;
char fbuf[MAXPATHLEN];
if (tTd(44, 4))
sm_dprintf("safefile(%s, uid=%d, gid=%d, flags=%lx, mode=%o):\n",
fn, (int) uid, (int) gid, flags, mode);
errno = 0;
if (sm_strlcpy(fbuf, fn, sizeof fbuf) >= sizeof fbuf)
{
if (tTd(44, 4))
sm_dprintf("\tpathname too long\n");
return ENAMETOOLONG;
}
fn = fbuf;
if (st == NULL)
st = &fstbuf;
/* ignore SFF_SAFEDIRPATH if we are debugging */
if (RealUid != 0 && RunAsUid == RealUid)
flags &= ~SFF_SAFEDIRPATH;
/* first check to see if the file exists at all */
# if HASLSTAT
if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
: stat(fn, st)) < 0)
# else /* HASLSTAT */
if (stat(fn, st) < 0)
# endif /* HASLSTAT */
{
file_errno = errno;
}
else if (bitset(SFF_SETUIDOK, flags) &&
!bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode) &&
S_ISREG(st->st_mode))
{
/*
** If final file is set-user-ID, run as the owner of that
** file. Gotta be careful not to reveal anything too
** soon here!
*/
# ifdef SUID_ROOT_FILES_OK
if (bitset(S_ISUID, st->st_mode))
# else /* SUID_ROOT_FILES_OK */
if (bitset(S_ISUID, st->st_mode) && st->st_uid != 0 &&
st->st_uid != TrustedUid)
# endif /* SUID_ROOT_FILES_OK */
{
uid = st->st_uid;
user = NULL;
}
# ifdef SUID_ROOT_FILES_OK
if (bitset(S_ISGID, st->st_mode))
# else /* SUID_ROOT_FILES_OK */
if (bitset(S_ISGID, st->st_mode) && st->st_gid != 0)
# endif /* SUID_ROOT_FILES_OK */
gid = st->st_gid;
}
checkpath = !bitset(SFF_NOPATHCHECK, flags) ||
(uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags));
if (bitset(SFF_NOWLINK, flags) && !bitset(SFF_SAFEDIRPATH, flags))
{
int ret;
/* check the directory */
p = strrchr(fn, '/');
if (p == NULL)
{
ret = safedirpath(".", uid, gid, user,
flags|SFF_SAFEDIRPATH, 0, 0);
}
else
{
*p = '\0';
ret = safedirpath(fn, uid, gid, user,
flags|SFF_SAFEDIRPATH, 0, 0);
*p = '/';
}
if (ret == 0)
{
/* directory is safe */
checkpath = false;
}
else
{
# if HASLSTAT
/* Need lstat() information if called stat() before */
if (!bitset(SFF_NOSLINK, flags) && lstat(fn, st) < 0)
{
ret = errno;
if (tTd(44, 4))
sm_dprintf("\t%s\n", sm_errstring(ret));
return ret;
}
# endif /* HASLSTAT */
/* directory is writable: disallow links */
flags |= SFF_NOLINK;
}
}
if (checkpath)
{
int ret;
p = strrchr(fn, '/');
if (p == NULL)
{
ret = safedirpath(".", uid, gid, user, flags, 0, 0);
}
else
{
*p = '\0';
ret = safedirpath(fn, uid, gid, user, flags, 0, 0);
*p = '/';
}
if (ret != 0)
return ret;
}
/*
** If the target file doesn't exist, check the directory to
** ensure that it is writable by this user.
*/
if (file_errno != 0)
{
int ret = file_errno;
char *dir = fn;
if (tTd(44, 4))
sm_dprintf("\t%s\n", sm_errstring(ret));
errno = 0;
if (!bitset(SFF_CREAT, flags) || file_errno != ENOENT)
return ret;
/* check to see if legal to create the file */
p = strrchr(dir, '/');
if (p == NULL)
dir = ".";
else if (p == dir)
dir = "/";
else
*p = '\0';
if (stat(dir, &stbuf) >= 0)
{
int md = S_IWRITE|S_IEXEC;
ret = 0;
if (stbuf.st_uid == uid)
/* EMPTY */
;
else if (uid == 0 && stbuf.st_uid == TrustedUid)
/* EMPTY */
;
else
{
md >>= 3;
if (stbuf.st_gid == gid)
/* EMPTY */
;
# ifndef NO_GROUP_SET
else if (user != NULL && !DontInitGroups &&
((gr != NULL &&
gr->gr_gid == stbuf.st_gid) ||
(gr = getgrgid(stbuf.st_gid)) != NULL))
{
register char **gp;
for (gp = gr->gr_mem; *gp != NULL; gp++)
if (strcmp(*gp, user) == 0)
break;
if (*gp == NULL)
md >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
md >>= 3;
}
if ((stbuf.st_mode & md) != md)
ret = errno = EACCES;
}
else
ret = errno;
if (tTd(44, 4))
sm_dprintf("\t[final dir %s uid %d mode %lo] %s\n",
dir, (int) stbuf.st_uid,
(unsigned long) stbuf.st_mode,
sm_errstring(ret));
if (p != NULL)
*p = '/';
st->st_mode = ST_MODE_NOFILE;
return ret;
}
# ifdef S_ISLNK
if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[slink mode %lo]\tE_SM_NOSLINK\n",
(unsigned long) st->st_mode);
return E_SM_NOSLINK;
}
# endif /* S_ISLNK */
if (bitset(SFF_REGONLY, flags) && !S_ISREG(st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[non-reg mode %lo]\tE_SM_REGONLY\n",
(unsigned long) st->st_mode);
return E_SM_REGONLY;
}
if (bitset(SFF_NOGWFILES, flags) &&
bitset(S_IWGRP, st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[write bits %lo]\tE_SM_GWFILE\n",
(unsigned long) st->st_mode);
return E_SM_GWFILE;
}
if (bitset(SFF_NOWWFILES, flags) &&
bitset(S_IWOTH, st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[write bits %lo]\tE_SM_WWFILE\n",
(unsigned long) st->st_mode);
return E_SM_WWFILE;
}
if (bitset(SFF_NOGRFILES, flags) && bitset(S_IRGRP, st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[read bits %lo]\tE_SM_GRFILE\n",
(unsigned long) st->st_mode);
return E_SM_GRFILE;
}
if (bitset(SFF_NOWRFILES, flags) && bitset(S_IROTH, st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[read bits %lo]\tE_SM_WRFILE\n",
(unsigned long) st->st_mode);
return E_SM_WRFILE;
}
if (!bitset(SFF_EXECOK, flags) &&
bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) &&
bitset(S_IXUSR|S_IXGRP|S_IXOTH, st->st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[exec bits %lo]\tE_SM_ISEXEC\n",
(unsigned long) st->st_mode);
return E_SM_ISEXEC;
}
if (bitset(SFF_NOHLINK, flags) && st->st_nlink != 1)
{
if (tTd(44, 4))
sm_dprintf("\t[link count %d]\tE_SM_NOHLINK\n",
(int) st->st_nlink);
return E_SM_NOHLINK;
}
if (uid == 0 && bitset(SFF_OPENASROOT, flags))
/* EMPTY */
;
else if (uid == 0 && !bitset(SFF_ROOTOK, flags))
mode >>= 6;
else if (st->st_uid == uid)
/* EMPTY */
;
else if (uid == 0 && st->st_uid == TrustedUid)
/* EMPTY */
;
else
{
mode >>= 3;
if (st->st_gid == gid)
/* EMPTY */
;
# ifndef NO_GROUP_SET
else if (user != NULL && !DontInitGroups &&
((gr != NULL && gr->gr_gid == st->st_gid) ||
(gr = getgrgid(st->st_gid)) != NULL))
{
register char **gp;
for (gp = gr->gr_mem; *gp != NULL; gp++)
if (strcmp(*gp, user) == 0)
break;
if (*gp == NULL)
mode >>= 3;
}
# endif /* ! NO_GROUP_SET */
else
mode >>= 3;
}
if (tTd(44, 4))
sm_dprintf("\t[uid %d, nlink %d, stat %lo, mode %lo] ",
(int) st->st_uid, (int) st->st_nlink,
(unsigned long) st->st_mode, (unsigned long) mode);
if ((st->st_uid == uid || st->st_uid == 0 ||
st->st_uid == TrustedUid ||
!bitset(SFF_MUSTOWN, flags)) &&
(st->st_mode & mode) == mode)
{
if (tTd(44, 4))
sm_dprintf("\tOK\n");
return 0;
}
if (tTd(44, 4))
sm_dprintf("\tEACCES\n");
return EACCES;
}
/*
** SAFEDIRPATH -- check to make sure a path to a directory is safe
**
** Safe means not writable and owned by the right folks.
**
** Parameters:
** fn -- filename to check.
** uid -- user id to compare against.
** gid -- group id to compare against.
** user -- user name to compare against (used for group
** sets).
** flags -- modifiers:
** SFF_ROOTOK -- ok to use root permissions to open.
** SFF_SAFEDIRPATH -- writable directories are considered
** to be fatal errors.
** level -- symlink recursive level.
** offset -- offset into fn to start checking from.
**
** Returns:
** 0 -- if the directory path is "safe".
** else -- an error number associated with the path.
*/
int
safedirpath(fn, uid, gid, user, flags, level, offset)
char *fn;
UID_T uid;
GID_T gid;
char *user;
long flags;
int level;
int offset;
{
int ret = 0;
int mode = S_IWOTH;
char save = '\0';
char *saveptr = NULL;
char *p, *enddir;
register struct group *gr = NULL;
char s[MAXLINKPATHLEN];
struct stat stbuf;
/* make sure we aren't in a symlink loop */
if (level > MAXSYMLINKS)
return ELOOP;
if (level < 0 || offset < 0 || offset > strlen(fn))
return EINVAL;
/* special case root directory */
if (*fn == '\0')
fn = "/";
if (tTd(44, 4))
sm_dprintf("safedirpath(%s, uid=%ld, gid=%ld, flags=%lx, level=%d, offset=%d):\n",
fn, (long) uid, (long) gid, flags, level, offset);
if (!bitnset(DBS_GROUPWRITABLEDIRPATHSAFE, DontBlameSendmail))
mode |= S_IWGRP;
/* Make a modifiable copy of the filename */
if (sm_strlcpy(s, fn, sizeof s) >= sizeof s)
return EINVAL;
p = s + offset;
while (p != NULL)
{
/* put back character */
if (saveptr != NULL)
{
*saveptr = save;
saveptr = NULL;
p++;
}
if (*p == '\0')
break;
p = strchr(p, '/');
/* Special case for root directory */
if (p == s)
{
save = *(p + 1);
saveptr = p + 1;
*(p + 1) = '\0';
}
else if (p != NULL)
{
save = *p;
saveptr = p;
*p = '\0';
}
/* Heuristic: . and .. have already been checked */
enddir = strrchr(s, '/');
if (enddir != NULL &&
(strcmp(enddir, "/..") == 0 ||
strcmp(enddir, "/.") == 0))
continue;
if (tTd(44, 20))
sm_dprintf("\t[dir %s]\n", s);
# if HASLSTAT
ret = lstat(s, &stbuf);
# else /* HASLSTAT */
ret = stat(s, &stbuf);
# endif /* HASLSTAT */
if (ret < 0)
{
ret = errno;
break;
}
# ifdef S_ISLNK
/* Follow symlinks */
if (S_ISLNK(stbuf.st_mode))
{
int linklen;
char *target;
char buf[MAXPATHLEN];
char fullbuf[MAXLINKPATHLEN];
memset(buf, '\0', sizeof buf);
linklen = readlink(s, buf, sizeof buf);
if (linklen < 0)
{
ret = errno;
break;
}
if (linklen >= sizeof buf)
{
/* file name too long for buffer */
ret = errno = EINVAL;
break;
}
offset = 0;
if (*buf == '/')
{
target = buf;
/* If path is the same, avoid rechecks */
while (s[offset] == buf[offset] &&
s[offset] != '\0')
offset++;
if (s[offset] == '\0' && buf[offset] == '\0')
{
/* strings match, symlink loop */
return ELOOP;
}
/* back off from the mismatch */
if (offset > 0)
offset--;
/* Make sure we are at a directory break */
if (offset > 0 &&
s[offset] != '/' &&
s[offset] != '\0')
{
while (buf[offset] != '/' &&
offset > 0)
offset--;
}
if (offset > 0 &&
s[offset] == '/' &&
buf[offset] == '/')
{
/* Include the trailing slash */
offset++;
}
}
else
{
char *sptr;
sptr = strrchr(s, '/');
if (sptr != NULL)
{
*sptr = '\0';
offset = sptr + 1 - s;
if (sm_strlcpyn(fullbuf,
sizeof fullbuf, 2,
s, "/") >=
sizeof fullbuf ||
sm_strlcat(fullbuf, buf,
sizeof fullbuf) >=
sizeof fullbuf)
{
ret = EINVAL;
break;
}
*sptr = '/';
}
else
{
if (sm_strlcpy(fullbuf, buf,
sizeof fullbuf) >=
sizeof fullbuf)
{
ret = EINVAL;
break;
}
}
target = fullbuf;
}
ret = safedirpath(target, uid, gid, user, flags,
level + 1, offset);
if (ret != 0)
break;
/* Don't check permissions on the link file itself */
continue;
}
#endif /* S_ISLNK */
if ((uid == 0 || bitset(SFF_SAFEDIRPATH, flags)) &&
#ifdef S_ISVTX
!(bitnset(DBS_TRUSTSTICKYBIT, DontBlameSendmail) &&
bitset(S_ISVTX, stbuf.st_mode)) &&
#endif /* S_ISVTX */
bitset(mode, stbuf.st_mode))
{
if (tTd(44, 4))
sm_dprintf("\t[dir %s] mode %lo ",
s, (unsigned long) stbuf.st_mode);
if (bitset(SFF_SAFEDIRPATH, flags))
{
if (bitset(S_IWOTH, stbuf.st_mode))
ret = E_SM_WWDIR;
else
ret = E_SM_GWDIR;
if (tTd(44, 4))
sm_dprintf("FATAL\n");
break;
}
if (tTd(44, 4))
sm_dprintf("WARNING\n");
if (Verbose > 1)
message("051 WARNING: %s writable directory %s",
bitset(S_IWOTH, stbuf.st_mode)
? "World"
: "Group",
s);
}
if (uid == 0 && !bitset(SFF_ROOTOK|SFF_OPENASROOT, flags))
{
if (bitset(S_IXOTH, stbuf.st_mode))
continue;
ret = EACCES;
break;
}
/*
** Let OS determine access to file if we are not
** running as a privileged user. This allows ACLs
** to work. Also, if opening as root, assume we can
** scan the directory.
*/
if (geteuid() != 0 || bitset(SFF_OPENASROOT, flags))
continue;
if (stbuf.st_uid == uid &&
bitset(S_IXUSR, stbuf.st_mode))
continue;
if (stbuf.st_gid == gid &&
bitset(S_IXGRP, stbuf.st_mode))
continue;
# ifndef NO_GROUP_SET
if (user != NULL && !DontInitGroups &&
((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
(gr = getgrgid(stbuf.st_gid)) != NULL))
{
register char **gp;
for (gp = gr->gr_mem; gp != NULL && *gp != NULL; gp++)
if (strcmp(*gp, user) == 0)
break;
if (gp != NULL && *gp != NULL &&
bitset(S_IXGRP, stbuf.st_mode))
continue;
}
# endif /* ! NO_GROUP_SET */
if (!bitset(S_IXOTH, stbuf.st_mode))
{
ret = EACCES;
break;
}
}
if (tTd(44, 4))
sm_dprintf("\t[dir %s] %s\n", fn,
ret == 0 ? "OK" : sm_errstring(ret));
return ret;
}
/*
** SAFEOPEN -- do a file open with extra checking
**
** Parameters:
** fn -- the file name to open.
** omode -- the open-style mode flags.
** cmode -- the create-style mode flags.
** sff -- safefile flags.
**
** Returns:
** Same as open.
*/
int
safeopen(fn, omode, cmode, sff)
char *fn;
int omode;
int cmode;
long sff;
{
#if !NOFTRUNCATE
bool truncate;
#endif /* !NOFTRUNCATE */
int rval;
int fd;
int smode;
struct stat stb;
if (tTd(44, 10))
sm_dprintf("safeopen: fn=%s, omode=%x, cmode=%x, sff=%lx\n",
fn, omode, cmode, sff);
if (bitset(O_CREAT, omode))
sff |= SFF_CREAT;
omode &= ~O_CREAT;
smode = 0;
switch (omode & O_ACCMODE)
{
case O_RDONLY:
smode = S_IREAD;
break;
case O_WRONLY:
smode = S_IWRITE;
break;
case O_RDWR:
smode = S_IREAD|S_IWRITE;
break;
default:
smode = 0;
break;
}
if (bitset(SFF_OPENASROOT, sff))
rval = safefile(fn, RunAsUid, RunAsGid, RunAsUserName,
sff, smode, &stb);
else
rval = safefile(fn, RealUid, RealGid, RealUserName,
sff, smode, &stb);
if (rval != 0)
{
errno = rval;
return -1;
}
if (stb.st_mode == ST_MODE_NOFILE && bitset(SFF_CREAT, sff))
omode |= O_CREAT | (bitset(SFF_NOTEXCL, sff) ? 0 : O_EXCL);
else if (bitset(SFF_CREAT, sff) && bitset(O_EXCL, omode))
{
/* The file exists so an exclusive create would fail */
errno = EEXIST;
return -1;
}
#if !NOFTRUNCATE
truncate = bitset(O_TRUNC, omode);
if (truncate)
omode &= ~O_TRUNC;
#endif /* !NOFTRUNCATE */
fd = dfopen(fn, omode, cmode, sff);
if (fd < 0)
return fd;
if (filechanged(fn, fd, &stb))
{
syserr("554 5.3.0 cannot open: file %s changed after open", fn);
(void) close(fd);
errno = E_SM_FILECHANGE;
return -1;
}
#if !NOFTRUNCATE
if (truncate &&
ftruncate(fd, (off_t) 0) < 0)
{
int save_errno;
save_errno = errno;
syserr("554 5.3.0 cannot open: file %s could not be truncated",
fn);
(void) close(fd);
errno = save_errno;
return -1;
}
#endif /* !NOFTRUNCATE */
return fd;
}
/*
** SAFEFOPEN -- do a file open with extra checking
**
** Parameters:
** fn -- the file name to open.
** omode -- the open-style mode flags.
** cmode -- the create-style mode flags.
** sff -- safefile flags.
**
** Returns:
** Same as fopen.
*/
SM_FILE_T *
safefopen(fn, omode, cmode, sff)
char *fn;
int omode;
int cmode;
long sff;
{
int fd;
int save_errno;
SM_FILE_T *fp;
int fmode;
switch (omode & O_ACCMODE)
{
case O_RDONLY:
fmode = SM_IO_RDONLY;
break;
case O_WRONLY:
if (bitset(O_APPEND, omode))
fmode = SM_IO_APPEND;
else
fmode = SM_IO_WRONLY;
break;
case O_RDWR:
if (bitset(O_TRUNC, omode))
fmode = SM_IO_RDWRTR;
else if (bitset(O_APPEND, omode))
fmode = SM_IO_APPENDRW;
else
fmode = SM_IO_RDWR;
break;
default:
syserr("554 5.3.5 safefopen: unknown omode %o", omode);
fmode = 0;
}
fd = safeopen(fn, omode, cmode, sff);
if (fd < 0)
{
save_errno = errno;
if (tTd(44, 10))
sm_dprintf("safefopen: safeopen failed: %s\n",
sm_errstring(errno));
errno = save_errno;
return NULL;
}
fp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
(void *) &fd, fmode, NULL);
if (fp != NULL)
return fp;
save_errno = errno;
if (tTd(44, 10))
{
sm_dprintf("safefopen: fdopen(%s, %d) failed: omode=%x, sff=%lx, err=%s\n",
fn, fmode, omode, sff, sm_errstring(errno));
}
(void) close(fd);
errno = save_errno;
return NULL;
}
/*
** FILECHANGED -- check to see if file changed after being opened
**
** Parameters:
** fn -- pathname of file to check.
** fd -- file descriptor to check.
** stb -- stat structure from before open.
**
** Returns:
** true -- if a problem was detected.
** false -- if this file is still the same.
*/
bool
filechanged(fn, fd, stb)
char *fn;
int fd;
struct stat *stb;
{
struct stat sta;
if (stb->st_mode == ST_MODE_NOFILE)
{
# if HASLSTAT && BOGUS_O_EXCL
/* only necessary if exclusive open follows symbolic links */
if (lstat(fn, stb) < 0 || stb->st_nlink != 1)
return true;
# else /* HASLSTAT && BOGUS_O_EXCL */
return false;
# endif /* HASLSTAT && BOGUS_O_EXCL */
}
if (fstat(fd, &sta) < 0)
return true;
if (sta.st_nlink != stb->st_nlink ||
sta.st_dev != stb->st_dev ||
sta.st_ino != stb->st_ino ||
# if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
sta.st_gen != stb->st_gen ||
# endif /* HAS_ST_GEN && 0 */
sta.st_uid != stb->st_uid ||
sta.st_gid != stb->st_gid)
{
if (tTd(44, 8))
{
sm_dprintf("File changed after opening:\n");
sm_dprintf(" nlink = %ld/%ld\n",
(long) stb->st_nlink, (long) sta.st_nlink);
sm_dprintf(" dev = %ld/%ld\n",
(long) stb->st_dev, (long) sta.st_dev);
sm_dprintf(" ino = %llu/%llu\n",
(ULONGLONG_T) stb->st_ino,
(ULONGLONG_T) sta.st_ino);
# if HAS_ST_GEN
sm_dprintf(" gen = %ld/%ld\n",
(long) stb->st_gen, (long) sta.st_gen);
# endif /* HAS_ST_GEN */
sm_dprintf(" uid = %ld/%ld\n",
(long) stb->st_uid, (long) sta.st_uid);
sm_dprintf(" gid = %ld/%ld\n",
(long) stb->st_gid, (long) sta.st_gid);
}
return true;
}
return false;
}
/*
** DFOPEN -- determined file open
**
** This routine has the semantics of open, except that it will
** keep trying a few times to make this happen. The idea is that
** on very loaded systems, we may run out of resources (inodes,
** whatever), so this tries to get around it.
*/
int
dfopen(filename, omode, cmode, sff)
char *filename;
int omode;
int cmode;
long sff;
{
register int tries;
int fd = -1;
struct stat st;
for (tries = 0; tries < 10; tries++)
{
(void) sleep((unsigned) (10 * tries));
errno = 0;
fd = open(filename, omode, cmode);
if (fd >= 0)
break;
switch (errno)
{
case ENFILE: /* system file table full */
case EINTR: /* interrupted syscall */
#ifdef ETXTBSY
case ETXTBSY: /* Apollo: net file locked */
#endif /* ETXTBSY */
continue;
}
break;
}
if (!bitset(SFF_NOLOCK, sff) &&
fd >= 0 &&
fstat(fd, &st) >= 0 &&
S_ISREG(st.st_mode))
{
int locktype;
/* lock the file to avoid accidental conflicts */
if ((omode & O_ACCMODE) != O_RDONLY)
locktype = LOCK_EX;
else
locktype = LOCK_SH;
if (bitset(SFF_NBLOCK, sff))
locktype |= LOCK_NB;
if (!lockfile(fd, filename, NULL, locktype))
{
int save_errno = errno;
(void) close(fd);
fd = -1;
errno = save_errno;
}
else
errno = 0;
}
return fd;
}