summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisplib.c20
-rwxr-xr-xshare/txr/stdlib/copy-file.tl184
-rw-r--r--txr.1307
3 files changed, 511 insertions, 0 deletions
diff --git a/lisplib.c b/lisplib.c
index dfe3d02e..cc4c3d8f 100644
--- a/lisplib.c
+++ b/lisplib.c
@@ -800,6 +800,25 @@ static val defset_set_entries(val dlt, val fun)
set_dlt_entries(dlt, name, fun);
return nil;
}
+
+static val copy_file_instantiate(val set_fun)
+{
+ funcall1(set_fun, nil);
+ load(format(nil, lit("~acopy-file"), stdlib_path, nao));
+ return nil;
+}
+
+static val copy_file_set_entries(val dlt, val fun)
+{
+ val name[] = {
+ lit("copy-path-opts"), lit("copy-file"), lit("copy-files"),
+ lit("copy-path-rec"), lit("remove-path-rec"),
+ nil
+ };
+ set_dlt_entries(dlt, name, fun);
+ return nil;
+}
+
val dlt_register(val dlt,
val (*instantiate)(val),
@@ -852,6 +871,7 @@ void lisplib_init(void)
dlt_register(dl_table, save_exe_instantiate, save_exe_set_entries);
dlt_register(dl_table, defset_instantiate, defset_set_entries);
+ dlt_register(dl_table, copy_file_instantiate, copy_file_set_entries);
reg_fun(intern(lit("try-load"), system_package), func_n1(lisplib_try_load));
}
diff --git a/share/txr/stdlib/copy-file.tl b/share/txr/stdlib/copy-file.tl
new file mode 100755
index 00000000..aceeb2e8
--- /dev/null
+++ b/share/txr/stdlib/copy-file.tl
@@ -0,0 +1,184 @@
+;; Copyright 2018-2020
+;; Kaz Kylheku <kaz@kylheku.com>
+;; Vancouver, Canada
+;; All rights reserved.
+;;
+;; Redistribution and use in source and binary forms, with or without
+;; modification, are permitted provided that the following conditions are met:
+;;
+;; 1. Redistributions of source code must retain the above copyright notice, this
+;; list of conditions and the following disclaimer.
+;;
+;; 2. Redistributions in binary form must reproduce the above copyright notice,
+;; this list of conditions and the following disclaimer in the documentation
+;; and/or other materials provided with the distribution.
+;;
+;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(eval-only
+ (defsymacro copy-size 65536)
+
+ (defpackage copy-file
+ (:fallback usr sys)
+ (:use-syms usr:perms usr:times usr:owner usr:symlinks))
+
+ (in-package copy-file))
+
+(defstruct copy-path-opts ()
+ perms times owner symlinks (euid (geteuid)))
+
+(defstruct copy-path-stack-node ()
+ path stat new-p)
+
+(defun make-copy-path-opts (opt-list)
+ (if opt-list
+ (let (opts)
+ (each ((opt opt-list))
+ (if (structp opt)
+ (set opts opt)
+ (progn
+ (unless opts
+ (set opts (new copy-path-opts)))
+ (caseql opt
+ (:perms (set opts.perms t))
+ (:times (set opts.times t))
+ (:owner (set opts.owner t))
+ (:symlinks (set opts.symlinks t))
+ (:all (set opts.perms t
+ opts.times t
+ opts.owner t
+ opts.symlinks t))
+ (t (error "~s: unrecognized option ~s" 'copy-path opt))))))
+ opts)
+ (load-time (new copy-path-opts))))
+
+(defun copy-file (from-path to-path : preserve-perms preserve-times)
+ (with-resources ((buf (make-buf copy-size)
+ (buf-set-length buf 0) (buf-trim buf))
+ (ist (open-file from-path "b") (close-stream ist))
+ (ost (open-file to-path "wb") (close-stream ost)))
+ (while (eql (len buf) copy-size)
+ (fill-buf-adjust buf 0 ist)
+ (put-buf buf 0 ost))
+ (when (or preserve-perms preserve-times)
+ (let ((st (fstat ist)))
+ (when preserve-perms
+ (chmod ost st.mode))
+ (when preserve-times
+ (flush-stream ost)
+ (utimes ost
+ st.atime (or st.atime-nsec 0)
+ st.mtime (or st.mtime-nsec 0)))))
+ nil))
+
+(defun copy-files (paths dest-dir : preserve-perms preserve-times)
+ (each ((path paths))
+ (while t
+ (catch**
+ (return (copy-file path (path-cat dest-dir (base-name path))
+ preserve-perms preserve-times))
+ (skip `skip copying @path` (exc . args) (return))
+ (retry `retry copying @path` (exc . args))))))
+
+(defun do-tweak-obj (to-path st opts link-p)
+ (when (and opts.perms (not link-p))
+ (chmod to-path st.mode))
+ (when opts.times
+ (lutimes to-path
+ st.atime (or st.atime-nsec 0)
+ st.mtime (or st.mtime-nsec 0)))
+ (when (and opts.owner
+ (or (zerop opts.euid)
+ (and (path-mine-p st)
+ (path-my-group-p st))))
+ (lchown to-path st.uid st.gid)))
+
+(defun do-copy-obj (from-path to-path st opts)
+ (let ((type (logand st.mode s-ifmt))
+ (initial-perms (if opts.perms #o700 #o777))
+ (tweak t))
+ (caseql* type
+ (s-ifreg
+ (copy-file from-path to-path opts.perms opts.times))
+ (s-ifsock
+ (mknod to-path (logior type initial-perms)))
+ (s-ififo
+ (mkfifo to-path initial-perms))
+ (s-iflnk
+ (if opts.symlinks
+ (symlink (readlink from-path) to-path)
+ (progn
+ (do-copy-obj from-path to-path (stat from-path) opts)
+ (set tweak nil))))
+ ((s-ifblk s-ifchr)
+ (mknod to-path (logior type initial-perms) st.rdev))
+ (s-ifdir
+ (ensure-dir to-path)))
+ (when tweak
+ (do-tweak-obj to-path st opts (eq type s-iflnk)))))
+
+(defun copy-path-rec (from-dir to-dir . opt-list)
+ (let* ((opts (make-copy-path-opts opt-list))
+ (dir-stack nil))
+ (unwind-protect
+ (ftw from-dir
+ (lambda (path type stat . rest)
+ (while t
+ (catch**
+ (let* ((rel-path (let ((p [path (len from-dir)..:]))
+ (if (pure-rel-path-p p) p [p 1..:])))
+ (tgt-path (path-cat to-dir rel-path)))
+ (unless (starts-with from-dir path)
+ (error "~s: problem with directory traversal" 'copy-path))
+ (caseql* type
+ ((ftw-dnr ftw-ns) (error "~s: unable to access ~s"
+ 'copy-path path))
+ (ftw-d (let ((new-p (ensure-dir tgt-path)))
+ (whilet ((top (car dir-stack))
+ ((and top
+ (not (starts-with tgt-path
+ top.path)))))
+ (do-tweak-obj top.path top.stat opts nil)
+ (pop dir-stack))
+ (push (new copy-path-stack-node
+ path tgt-path
+ stat stat
+ new-p new-p)
+ dir-stack)))
+ (t (iflet ((cur (car dir-stack)))
+ (unless cur.new-p
+ (remove-path tgt-path)))
+ (do-copy-obj path tgt-path stat opts)))
+ (return))
+ (skip `skip copying @path` (exc . args) (return))
+ (retry `retry copying @path` (exc . args)))))
+ ftw-phys)
+ (whilet ((top (pop dir-stack)))
+ (do-tweak-obj top.path top.stat opts nil)))))
+
+(defun remove-path-rec (path)
+ (ftw path
+ (lambda (path type stat . rest)
+ (while t
+ (catch**
+ (return
+ (caseql* type
+ ((ftw-dnr ftw-ns) (error "~s: unable to access ~s"
+ 'remove-rec path))
+ (ftw-dp (rmdir path))
+ (t (remove-path path))))
+ (skip `skip removing @path` (exc . args) (return))
+ (retry `retry copying @path` (exc . args)))))
+ (logior ftw-phys ftw-depth)))
+
+(eval-only
+ (merge-delete-package 'sys))
diff --git a/txr.1 b/txr.1
index 7575b05c..e6cb7758 100644
--- a/txr.1
+++ b/txr.1
@@ -58377,6 +58377,313 @@ value is available using the
.code errno
function.
+.SS* Unix Filesystem Complex Operations
+
+Functions in this category are complex functionality implemented using
+a combination of multiple calls into the host system's POSIX API.
+
+.coNP Functions @ copy-file and @ copy-files
+.synb
+.mets (copy-file < from-path < to-path >> [ perms-p <> [ times-p ]])
+.mets (copy-file < from-list < to-dir >> [ perms-p <> [ times-p ]])
+.syne
+.desc
+The
+.code copy-file
+function creates a replica of the file
+.code from-path
+at the destination path
+.metn to-path .
+
+Both paths are opened using
+.code open-file
+in binary mode, as if using
+.mono
+.meti (open-file < from-path "b")
+.onom
+and
+.mono
+.meti (open-file < to-path "wb")
+.onom
+respectively. Then bytes are read from one stream and written to the other,
+in blocks which whose size is a power of two at least as large as 16834.
+
+If the optional Boolean parameter
+.meta perms-p
+is specified, and is true, then the permissions of
+.meta from-path
+are propagated to
+.metn to-path .
+
+If the optional Boolean parameter
+.meta times-p
+is specified, and is true, then the access and modification timestamps of
+.meta from-path
+are propagated to
+.metn to-path .
+
+The
+.code copy-file
+function returns
+.code nil
+if it is successful, and throws an exception derived from
+.code file-error
+on failure.
+
+The
+.code copy-files
+function copies multiple files, whose pathnames are given by the list argument
+.meta from-list
+into the target directory whose path is given by
+.metn to-dir .
+
+The target directory must exist.
+
+For source each path in
+.metn from-list ,
+the
+.code copy-files
+function forms a target path by combining the base name of the
+source path with
+.metn target-dir .
+(See the
+.code base-name
+and
+.code path-cat
+functions).
+Then, the source path is copied to the resulting target path, as if by the
+.code copy-file
+function.
+
+The
+.code copy-files
+function returns
+.code nil
+if it is successful, and throws an exception derived from
+.code file-error
+on failure.
+
+Additionally,
+.code copy-files
+provides an internal catch for the
+.code retry
+and
+.code skip
+restart exceptions. If the caller, using a handler frame established by
+.codn handle ,
+catches an error emanating from the
+.code copy-files
+function, it can retry the failed operation by throwing the
+.code retry
+exception, or continue copying with the next file by throwing the
+.code skip
+exception.
+
+.TP* Example:
+
+.verb
+ ;; Copy all "/mnt/cdrom/*.jpg" files into "images" directory,
+ ;; preserving their time stamps,
+ ;; continuing the operation in the face of
+ ;; file-error exceptions.
+ (handle
+ (copy-files (glob "/mnt/cdrom/*.jpg") "images" nil t)
+ (file-error (throw 'skip)))
+.brev
+
+.coNP Function @ copy-path-rec
+.synb
+.mets (copy-path-rec < from-path < to-path << option *)
+.syne
+.desc
+The
+.code copy-path-rec
+function replicates a file system object identified by the path name
+.metn from-path ,
+creating a similar object named
+.metn to-path .
+
+If
+.code from-path
+is a directory, it is recursively traversed and its structure and content
+is replicated under
+.codn to-path .
+
+The
+.meta option
+arguments are keywords, which may be the following:
+.RS
+.IP :perms
+Propagate the permissions of all objects under
+.meta from-path
+onto their
+.meta to-path
+counterparts. In the absence of this option, the copied objects
+receive permissions with are calculated by applying the
+.code umask
+of the calling process to the maximally liberal.
+.IP :times
+Propagate the modification and access time stamps of all objects under
+.meta from-path
+onto their
+.meta to-path
+counterparts.
+.IP :symlinks
+Copy symbolic links literally rather than dereferencing them.
+Symbolic links are not altered in any way; their exact content
+is preserved. Thus, relative symlinks which point outside of the
+.meta from-path
+tree may turn into dangling symlinks in the
+.meta to-path
+tree.
+.IP :owner
+Propagate the ownership of all objects under
+.meta from-path
+to their
+.meta to-path
+counterparts. Ownership refers to the owner user ID and group ID.
+Without this option, the ownership of the copied objects is derived
+from the effective user ID and group ID of the calling process.
+Note that it is assumed that the host system may requires superuser
+privileges to set both ownerships IDs of an object, and to set them to an
+arbitrary value. An unprivileged process may not change the user ID of a file,
+and may only change the group ID of a file which they own, to one of the groups
+of which that process is a member, either via the effective GID, or the
+ancillary list. The
+.code copy-path-rec
+function tests whether the application is running under superuser privileges;
+if not, then it only honors the
+.code :owner
+option for those objects under
+.meta from-path
+which are owned by the caller, and owned by a group to
+which the caller belongs.
+Other objects are copied as if the
+.code :owner
+option were not in effect, avoiding an attempt to set their ownership
+that is likely to fail.
+.IP :all
+The
+.code :all
+keyword is a shorthand representing all of the options being applied:
+permissions, times, symlinks and ownership are replicated.
+.RE
+
+.IP
+The
+.code copy-path-rec
+function creates all necessary path name components required for
+.meta to-path
+to come into existence, as if by using the
+.code ensure-dir
+function.
+
+Whenever an object under
+.meta from-path
+has a counterpart in
+.meta to-path
+which already exists, the situation is handled as follows:
+.RS
+.IP 1.
+If a directory object is copied to an existing directory object,
+then that existing directory object is accepted as the copy, and
+the operation continues recursively within that directory. If any options are
+specified, then the requested attributes are propagated to that existing
+directory.
+.IP 2.
+If a non-directory object is copied to a directory object, the
+situation throws an exception: the
+.code copy-path-rec
+function refuses to delete an entire directory or subdirectory in order
+to make way for a file, symbolic link, special device or any other kind
+of non-directory object.
+.IP 3.
+If any object is copied to an existing non-directory object,
+that target object is removed first, then the copy operation proceeds.
+.RE
+Copying of files takes place similarly as what is described for the
+.code copy-file
+function.
+
+Special objects such as FIFOs, character devices, block devices and sockets
+are copied by creating a new, similar objects at the destination path.
+In the case of devices, the major and minor numbers of the copy are
+derived from the original, so that the copy refers to the same device.
+However, the copy of a socket or a FIFO is effectively a new, different
+endpoint because these objects are identified by their path name.
+Processes using the copy of a socket or a FIFO will not connect to
+processes which are working with the original.
+
+The
+.code copy-path-rec
+function returns
+.code nil
+if it is successful. It throws an exception derived from
+.code file-error
+when encountering failures.
+
+Additionally
+.code copy-path-rec
+provides an internal catch for the
+.code retry
+and
+.code skip
+restart exceptions. If the caller, using a handler frame established by
+.codn handle ,
+catches an error emanating from the
+.code copy-files
+function, it can retry the failed operation by throwing the
+.code retry
+exception, or continue copying with the next object by throwing the
+.code skip
+exception.
+
+.coNP Function @ remove-path-rec
+.synb
+.mets (remove-path-rec << path )
+.syne
+.desc
+The
+.code remove-path-rec
+function attempts to remove the filesystem object named by
+.metn path .
+If
+.meta path
+refers to a directory, that directory is recursively traversed
+to remove all of its contents, and is then removed.
+
+The
+.code remove-path-rec
+function returns
+.code nil
+if it is successful. It throws an exception derived from
+.code file-error
+when encountering failures.
+
+Additionally
+.code remove-path-rec
+provides an internal catch for the
+.code retry
+and
+.code skip
+restart exceptions. If the caller, using a handler frame established by
+.codn handle ,
+catches an error emanating from the
+.code copy-files
+function, it can retry the failed operation by throwing the
+.code retry
+exception, or continue removing other objects by throwing the
+.code skip
+exception. Skipping a failed remove operation may cause subsequent
+operations to fail. Notably, the failure to remove an item inside
+a directory means that removal of that directory itself will fail,
+and ultimately,
+.meta path
+will still exist when
+.code remove-path-rec
+completes and returns.
+
+
.SS* Unix Filesystem Object Existence, Type and Access Tests
The following functions all accept, as the