Files
cdesktop/cde/programs/dtfile/dtcopy/fsrtns.c
Jon Trulson 56b53a30a1 Reimplement reverted commit 7fa35c to fix readlink() issues
Original implementation:

Commit: 7fa35cA
dtfile: coverity CIDs 88363,88405,89140,89612; insecure readlink

That commit caused dtfile to be unable to resolve symbolic links and
was later reverted.  This commit reimplements the fixes correctly, and
should hopefully still resolve the coverity issues as well.
2018-04-10 19:12:58 -06:00

535 lines
12 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these librararies and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/* $TOG: fsrtns.c /main/6 1998/10/26 12:41:20 mgreess $ */
/************************************<+>*************************************
****************************************************************************
*
* FILE: fsrtns.c
*
*
* DESCRIPTION: Routines to manipulate files and directores
*
* FUNCTIONS: CopyDir
* CopyFile
* CopyLink
* CopyObject
* EmptyDir
* EraseObject
* fsCopy
* fsCopyLink
* fsEmpty
* fsErase
* fsMove
* fsRename
*
* (c) Copyright 1993, 1994, 1995 Hewlett-Packard Company
* (c) Copyright 1993, 1994, 1995 International Business Machines Corp.
* (c) Copyright 1993, 1994, 1995 Sun Microsystems, Inc.
* (c) Copyright 1993, 1994, 1995 Novell, Inc.
*
****************************************************************************
************************************<+>*************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <utime.h>
#include <dirent.h>
#include <sys/file.h>
#include <string.h>
#include "fsrtns.h"
/*--------------------------------------------------------------------
* Callback functions
*------------------------------------------------------------------*/
int (*progressCallback)(char *fname) = NULL;
int (*errorCallback)(char *fname, int errnum) = NULL;
int (*periodicCallback)() = NULL;
/*--------------------------------------------------------------------
* Local subroutines
*------------------------------------------------------------------*/
static int CopyObject(char *sourceP, char *targetP, int repl, int link);
static int EraseObject(char *nameP, int force);
static int
CopyFile(char *sourceP, char *targetP, int repl, struct stat *statP)
/* copy a file; if repl non-zero, overwrite any existing file */
{
int src, tgt;
int nread, nwrite;
char buffer[2048];
struct utimbuf ut;
int rc;
/* open source file for read */
src = open(sourceP, O_RDONLY, 0);
if (src < 0)
return errno;
/* create target file */
tgt = open(targetP, O_CREAT | O_EXCL | O_WRONLY, statP->st_mode & 0777);
if (tgt < 0 && errno == EEXIST && repl) {
rc = EraseObject(targetP, repl);
if (rc) {
close(src);
return rc;
}
tgt = open(targetP, O_CREAT | O_EXCL | O_WRONLY, statP->st_mode & 0777);
}
if (tgt < 0) {
rc = errno;
close(src);
return rc;
}
/* if we have root privileges, make sure file ownership is preserved */
if (geteuid() == 0) {
if (statP->st_uid != 0 || statP->st_gid != getegid()) {
rc = fchown(tgt, statP->st_uid, statP->st_gid);
if (rc) {
rc = errno;
close(src);
close(tgt);
return rc;
}
}
}
/* copy data */
for (;;) {
/* read data from source file */
do {
errno = 0;
nread = read(src, buffer, sizeof(buffer));
} while (nread < 0 && errno == EINTR);
if (nread <= 0)
break;
/* write data to target file */
do {
errno = 0;
nwrite = write(tgt, buffer, nread);
} while (nwrite < 0 && errno == EINTR);
if (nwrite != nread)
break;
if (periodicCallback)
if (periodicCallback() != 0) {
unlink(targetP);
close(src);
close(tgt);
return -1;
}
}
/* check if data copy ended abnormally */
rc = (nread == 0)? 0: (errno != 0)? errno: -1;
/* close files */
close(src);
if (rc) {
close(tgt);
return rc;
}
if (close(tgt) != 0)
return errno;
/* set target file attributes */
ut.actime = statP->st_atime;
ut.modtime = statP->st_mtime;
rc = utime(targetP, &ut);
return (rc != 0)? errno: 0;
}
static int
CopyLink(char *sourceP, char *targetP, int repl, struct stat *statP)
/* copy a symbolic link */
{
int l, rc;
char buf[PATH_MAX];
do {
errno = 0;
l = readlink(sourceP, buf, PATH_MAX - 1);
} while (l < 0 && errno == EINTR);
if (l < 0)
return errno;
buf[l] = 0;
if (symlink(buf, targetP) == 0)
return 0;
else if (errno != EEXIST || !repl)
return errno;
if ((rc = EraseObject(targetP, repl)) != 0)
return rc;
if (symlink(buf, targetP) == 0)
return 0;
else
return errno;
}
static int
CopyDir(char *sourceP, char *targetP, int repl, int link, struct stat *statP)
/* copy a directory and recursively all its subdirectories */
{
DIR *dirP; /* open directory */
struct dirent *entryP; /* directory entry */
char srcname[1024], tgtname[1024];
int srclen, tgtlen;
int rc;
/* open source directory */
dirP = opendir(sourceP);
if (dirP == NULL)
return errno;
/* create target directory */
rc = mkdir(targetP, statP->st_mode & 0777);
if (rc < 0 && errno == EEXIST && repl) {
rc = EraseObject(targetP, repl);
if (rc)
return rc;
rc = mkdir(targetP, statP->st_mode & 0777);
}
if (rc < 0) {
rc = errno;
closedir(dirP);
return rc;
}
/* if we have root privileges, make sure directory ownership is preserved */
if (geteuid() == 0) {
if (statP->st_uid != 0 || statP->st_gid != getegid()) {
rc = chown(targetP, statP->st_uid, statP->st_gid);
if (rc) {
rc = errno;
closedir(dirP);
return rc;
}
}
}
/* prepare source and target names */
strcpy(srcname, sourceP);
srclen = strlen(srcname);
if (srcname[srclen - 1] != '/')
srcname[srclen++] = '/';
strcpy(tgtname, targetP);
tgtlen = strlen(tgtname);
if (tgtname[tgtlen - 1] != '/')
tgtname[tgtlen++] = '/';
for (rc = 0; rc == 0; ) {
do {
errno = 0;
entryP = readdir(dirP);
} while (entryP == NULL && errno == EINTR);
if (entryP == NULL) {
rc = errno;
break;
}
if (strcmp(entryP->d_name, ".") == 0 || strcmp(entryP->d_name, "..") == 0)
continue;
strcpy(srcname + srclen, entryP->d_name);
strcpy(tgtname + tgtlen, entryP->d_name);
rc = CopyObject(srcname, tgtname, repl, link);
}
closedir(dirP);
return rc;
}
static int
CopyObject(char *sourceP, char *targetP, int repl, int link)
/* copy a directory, file, or symbolic link */
{
struct stat src_stat;
int rc;
if (progressCallback)
if (progressCallback(sourceP) != 0)
return -1;
if (periodicCallback)
if (periodicCallback() != 0)
return -1;
if (lstat(sourceP, &src_stat) < 0)
rc = errno;
else {
copy_switch:
switch(src_stat.st_mode & S_IFMT) {
case S_IFDIR:
rc = CopyDir(sourceP, targetP, repl, link, &src_stat);
break;
case S_IFREG:
rc = CopyFile(sourceP, targetP, repl, &src_stat);
break;
case S_IFLNK:
if (link)
rc = CopyLink(sourceP, targetP, repl, &src_stat);
else if (stat(sourceP, &src_stat) < 0)
rc = errno;
else
goto copy_switch;
break;
default:
rc = EINVAL;
}
}
/*
* Return code zero means everything is ok;
* return code -1 means the operation is aborted.
* In either case, propagated the return code up.
*/
if (rc <= 0)
return rc;
/*
* Return code > 0 means an error occurred in the last operation.
* Call the the error callback function. If the callback returns
* zero, we return zero; this will cause the error to be ignored.
* Otherwise we return -1 to signal that the operation is aborted.
*/
if (!errorCallback)
return rc;
else if (errorCallback(sourceP, rc) == 0)
return 0;
else
return -1;
}
int
EmptyDir(char *sourceP, int rm, int force)
{
DIR *dirP; /* open directory */
struct dirent *entryP; /* directory entry */
char srcname[1024];
int srclen;
int rc;
/* open source directory */
dirP = opendir(sourceP);
if (dirP == NULL)
return errno;
/* prepare source name */
strcpy(srcname, sourceP);
srclen = strlen(srcname);
if (srcname[srclen - 1] != '/')
srcname[srclen++] = '/';
rc = 0;
while (rc == 0 && (entryP = readdir(dirP)) != NULL) {
if (strcmp(entryP->d_name, ".") == 0 || strcmp(entryP->d_name, "..") == 0)
continue;
strcpy(srcname + srclen, entryP->d_name);
rc = EraseObject(srcname, force);
}
closedir(dirP);
if (rc == 0 && rm) {
rc = rmdir(sourceP);
if (rc < 0)
rc = errno;
}
return rc;
}
static int
EraseObject(char *nameP, int force)
{
struct stat src_stat;
int rc = 0;
if (periodicCallback)
if (periodicCallback() != 0)
return -1;
if (lstat(nameP, &src_stat) < 0)
rc = errno;
else if ((src_stat.st_mode & S_IFMT) == S_IFDIR) {
if (access(nameP, X_OK|W_OK))
return errno;
rc = EmptyDir(nameP, 1, force);
}
else {
if (!force && access(nameP, W_OK))
return errno;
if (unlink(nameP))
rc = errno;
}
/*
* Return code zero means everything is ok;
* return code -1 means the operation is aborted.
* In either case, propagated the return code up.
*/
if (rc <= 0)
return rc;
/*
* Return code > 0 means an error occurred in the last operation.
* Call the the error callback function. If the callback returns
* zero, we return zero; this will cause the error to be ignored.
* Otherwise we return -1 to signal that the operation is aborted.
*/
if (!errorCallback)
return rc;
else if (errorCallback(nameP, rc) == 0)
return 0;
else
return -1;
}
/*--------------------------------------------------------------------
* Exported routines
*------------------------------------------------------------------*/
void
fsRename(char *sourceP, char *targetP, int replace, int *rcP)
{
int rc;
struct stat buf;
if (!replace) {
/* return error if target file already exists */
rc = lstat(targetP, &buf);
if (rc == 0) {
*rcP = EEXIST;
return;
} else if (errno != ENOENT) {
*rcP = errno;
return;
}
}
*rcP = rename(sourceP, targetP);
if (*rcP < 0)
*rcP = errno;
if (replace && *rcP == ENOTDIR || *rcP == EISDIR) {
/* error reason: tried to replace file by directory or vice versa */
*rcP = EraseObject(targetP, replace);
if (*rcP < 0)
return;
*rcP = rename(sourceP, targetP);
if (*rcP < 0)
*rcP = errno;
}
}
void
fsMove(char *sourceP, char *targetP, int replace, int *rcP)
{
/* try to rename */
fsRename(sourceP, targetP, replace, rcP);
if (*rcP == 0 || *rcP != EXDEV)
return;
/* source and target on different file systems: do copy + erase */
{
/* first check if we have write permission in the source directory */
char dir[1024], *p;
strcpy(dir, sourceP);
p = strrchr(dir, '/');
if (p == 0)
strcpy(dir, ".");
else if (p == dir)
strcpy(dir, "/");
else
*p = 0;
if (access(dir, W_OK) != 0) {
*rcP = errno;
return;
}
}
*rcP = CopyObject(sourceP, targetP, replace, 1);
if (*rcP != 0)
return;
*rcP = EraseObject(sourceP, replace);
}
void
fsCopy(char *sourceP, char *targetP, int replace, int *rcP)
{
*rcP = CopyObject(sourceP, targetP, replace, 0);
}
void
fsCopyLink(char *sourceP, char *targetP, int replace, int *rcP)
{
*rcP = CopyObject(sourceP, targetP, replace, 1);
}
void
fsErase(char *nameP, int *rcP, int force)
{
*rcP = EraseObject(nameP, force);
}
void
fsEmpty(char *nameP, int *rcP)
{
*rcP = EmptyDir(nameP, 0, 0);
}