+
+ while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+ wcount = write(to_fd, buf, rcount);
+ if (rcount != wcount || wcount == -1) {
+ error(to.p_path);
+ break;
+ }
+ }
+ if (rcount < 0)
+ error(from.p_path);
+ if (pflag)
+ setfile(fs, to_fd);
+ /*
+ * If the source was setuid or setgid, lose the bits unless the
+ * copy is owned by the same user and group.
+ */
+ else if (fs->st_mode & (S_ISUID|S_ISGID) && fs->st_uid == myuid)
+ if (fstat(to_fd, &to_stat))
+ error(to.p_path);
+#define RETAINBITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)
+ else if (fs->st_gid == to_stat.st_gid && fchmod(to_fd,
+ fs->st_mode & RETAINBITS & ~myumask))
+ error(to.p_path);
+ (void)close(from_fd);
+ if (close(to_fd))
+ error(to.p_path);
+}
+
+copy_dir()
+{
+ struct stat from_stat;
+ struct dirent *dp, **dir_list;
+ register int dir_cnt, i;
+ char *old_from, *old_to;
+
+ dir_cnt = scandir(from.p_path, &dir_list, NULL, NULL);
+ if (dir_cnt == -1) {
+ (void)fprintf(stderr, "%s: can't read directory %s.\n",
+ progname, from.p_path);
+ exit_val = 1;
+ }
+
+ /*
+ * Instead of handling directory entries in the order they appear
+ * on disk, do non-directory files before directory files.
+ * There are two reasons to do directories last. The first is
+ * efficiency. Files tend to be in the same cylinder group as
+ * their parent, whereas directories tend not to be. Copying files
+ * all at once reduces seeking. Second, deeply nested tree's
+ * could use up all the file descriptors if we didn't close one
+ * directory before recursivly starting on the next.
+ */
+ /* copy files */
+ for (i = 0; i < dir_cnt; ++i) {
+ dp = dir_list[i];
+ if (dp->d_namlen <= 2 && dp->d_name[0] == '.'
+ && (dp->d_name[1] == NULL || dp->d_name[1] == '.'))
+ goto done;
+ old_from = path_append(&from, dp->d_name, (int)dp->d_namlen);
+ if (!old_from) {
+ exit_val = 1;
+ goto done;
+ }
+
+ if (statfcn(from.p_path, &from_stat) < 0) {
+ error(dp->d_name);
+ path_restore(&from, old_from);
+ goto done;
+ }
+ if (S_ISDIR(from_stat.st_mode)) {
+ path_restore(&from, old_from);
+ continue;
+ }
+ old_to = path_append(&to, dp->d_name, (int)dp->d_namlen);
+ if (old_to) {
+ copy();
+ path_restore(&to, old_to);
+ } else
+ exit_val = 1;
+ path_restore(&from, old_from);
+done: dir_list[i] = NULL;
+ (void)free((void *)dp);
+ }
+
+ /* copy directories */
+ for (i = 0; i < dir_cnt; ++i) {
+ dp = dir_list[i];
+ if (!dp)
+ continue;
+ old_from = path_append(&from, dp->d_name, (int) dp->d_namlen);
+ if (!old_from) {
+ exit_val = 1;
+ (void)free((void *)dp);
+ continue;
+ }
+ old_to = path_append(&to, dp->d_name, (int) dp->d_namlen);
+ if (!old_to) {
+ exit_val = 1;
+ (void)free((void *)dp);
+ path_restore(&from, old_from);
+ continue;
+ }
+ copy();
+ free((void *)dp);
+ path_restore(&from, old_from);
+ path_restore(&to, old_to);
+ }
+ free((void *)dir_list);
+}
+
+copy_link(exists)
+ int exists;
+{
+ int len;
+ char link[MAXPATHLEN];
+
+ if ((len = readlink(from.p_path, link, sizeof(link))) == -1) {
+ error(from.p_path);
+ return;
+ }
+ link[len] = '\0';
+ if (exists && unlink(to.p_path)) {
+ error(to.p_path);
+ return;
+ }
+ if (symlink(link, to.p_path)) {
+ error(link);
+ return;
+ }
+}
+
+copy_fifo(from_stat, exists)
+ struct stat *from_stat;
+ int exists;
+{
+ if (exists && unlink(to.p_path)) {
+ error(to.p_path);
+ return;
+ }
+ if (mkfifo(to.p_path, from_stat->st_mode)) {
+ error(to.p_path);
+ return;
+ }
+ if (pflag)
+ setfile(from_stat, 0);
+}
+
+copy_special(from_stat, exists)
+ struct stat *from_stat;
+ int exists;
+{
+ if (exists && unlink(to.p_path)) {
+ error(to.p_path);
+ return;
+ }
+ if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
+ error(to.p_path);
+ return;
+ }
+ if (pflag)
+ setfile(from_stat, 0);
+}
+
+setfile(fs, fd)
+ register struct stat *fs;
+ int fd;
+{
+ static struct timeval tv[2];
+ char path[100];
+
+ fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
+
+ tv[0].tv_sec = fs->st_atime;
+ tv[1].tv_sec = fs->st_mtime;
+ if (utimes(to.p_path, tv)) {
+ (void)snprintf(path, sizeof(path), "utimes: %s", to.p_path);
+ error(path);
+ }
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
+ chown(to.p_path, fs->st_uid, fs->st_gid)) {
+ if (errno != EPERM) {
+ (void)snprintf(path, sizeof(path),
+ "chown: %s", to.p_path);
+ error(path);
+ }
+ fs->st_mode &= ~(S_ISUID|S_ISGID);
+ }
+ if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
+ (void)snprintf(path, sizeof(path), "chown: %s", to.p_path);
+ error(path);
+ }
+}
+
+error(s)
+ char *s;
+{
+ exit_val = 1;
+ (void)fprintf(stderr, "%s: %s: %s\n", progname, s, strerror(errno));
+}
+
+usage()
+{
+ (void)fprintf(stderr,
+"usage: cp [-Rfhip] src target;\n or: cp [-Rfhip] src1 ... srcN directory\n");
+ exit(1);