View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2002-2018, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9    All rights reserved.
   10
   11    Redistribution and use in source and binary forms, with or without
   12    modification, are permitted provided that the following conditions
   13    are met:
   14
   15    1. Redistributions of source code must retain the above copyright
   16       notice, this list of conditions and the following disclaimer.
   17
   18    2. Redistributions in binary form must reproduce the above copyright
   19       notice, this list of conditions and the following disclaimer in
   20       the documentation and/or other materials provided with the
   21       distribution.
   22
   23    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   24    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   25    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   26    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   27    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   28    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   29    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   30    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   31    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   32    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   33    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   34    POSSIBILITY OF SUCH DAMAGE.
   35*/
   36
   37:- module(files_ex,
   38          [ set_time_file/3,            % +File, -OldTimes, +NewTimes
   39            link_file/3,                % +OldPath, +NewPath, +Type
   40            chmod/2,                    % +File, +Mode
   41            relative_file_name/3,       % ?AbsPath, +RelTo, ?RelPath
   42            directory_file_path/3,      % +Dir, +File, -Path
   43            directory_member/3,		% +Dir, -Member, +Options
   44            copy_file/2,                % +From, +To
   45            make_directory_path/1,      % +Directory
   46            copy_directory/2,           % +Source, +Destination
   47            delete_directory_and_contents/1, % +Dir
   48            delete_directory_contents/1 % +Dir
   49          ]).   50:- use_module(library(apply)).   51:- use_module(library(error)).

Extended operations on files

This module provides additional operations on files. This covers both more obscure and possible non-portable low-level operations and high-level utilities.

Using these Prolog primitives is typically to be preferred over using operating system primitives through shell/1 or process_create/3 because (1) there are no potential file name quoting issues, (2) there is no dependency on operating system commands and (3) using the implementations from this library is usually faster. */

   66:- predicate_options(directory_member/3, 3,
   67                     [ recursive(boolean),
   68                       follow_links(boolean),
   69                       file_type(atom),
   70                       extensions(list(atom)),
   71                       file_errors(oneof([fail,warning,error])),
   72                       access(oneof([read,write,execute])),
   73                       matches(text),
   74                       exclude(text),
   75                       exclude_directory(text),
   76                       hidden(boolean)
   77                     ]).   78
   79
   80:- use_foreign_library(foreign(files), install_files).
 set_time_file(+File, -OldTimes, +NewTimes) is det
Query and set POSIX time attributes of a file. Both OldTimes and NewTimes are lists of option-terms. Times are represented in SWI-Prolog's standard floating point numbers. New times may be specified as now to indicate the current time. Defined options are:
access(Time)
Describes the time of last access of the file. This value can be read and written.
modified(Time)
Describes the time the contents of the file was last modified. This value can be read and written.
changed(Time)
Describes the time the file-structure itself was changed by adding (link()) or removing (unlink()) names.

Below are some example queries. The first retrieves the access-time, while the second sets the last-modified time to the current time.

?- set_time_file(foo, [access(Access)], []).
?- set_time_file(foo, [], [modified(now)]).
 link_file(+OldPath, +NewPath, +Type) is det
Create a link in the filesystem from NewPath to OldPath. Type defines the type of link and is one of hard or symbolic.

With some limitations, these functions also work on Windows. First of all, the underlying filesystem must support links. This requires NTFS. Second, symbolic links are only supported in Vista and later.

Errors
- domain_error(link_type, Type) if the requested link-type is unknown or not supported on the target OS.
 relative_file_name(+Path:atom, +RelToFile:atom, -RelPath:atom) is det
relative_file_name(-Path:atom, +RelToFile:atom, +RelPath:atom) is det
True when RelPath is Path, relative to the file RelToFile. Path and RelTo are first handed to absolute_file_name/2, which makes the absolute and canonical. Below are two examples:
?- relative_file_name('/home/janw/nice',
                      '/home/janw/deep/dir/file', Path).
Path = '../../nice'.

?- relative_file_name(Path, '/home/janw/deep/dir/file', '../../nice').
Path = '/home/janw/nice'.

Add a terminating / to get a path relative to a directory, e.g.

?- relative_file_name('/home/janw/deep/dir/file', './', Path).
Path = 'deep/dir/file'.
Arguments:
All- paths must be in canonical POSIX notation, i.e., using / to separate segments in the path. See prolog_to_os_filename/2.
bug
- It would probably have been cleaner to use a directory as second argument. We can not do such dynamically as this predicate is defined as a syntactical operation, which implies it may be used for non-existing paths and URLs.
  152relative_file_name(Path, RelTo, RelPath) :- % +,+,-
  153    nonvar(Path),
  154    !,
  155    absolute_file_name(Path, AbsPath),
  156    absolute_file_name(RelTo, AbsRelTo),
  157    atomic_list_concat(PL, /, AbsPath),
  158    atomic_list_concat(RL, /, AbsRelTo),
  159    delete_common_prefix(PL, RL, PL1, PL2),
  160    to_dot_dot(PL2, DotDot, PL1),
  161    atomic_list_concat(DotDot, /, RelPath).
  162relative_file_name(Path, RelTo, RelPath) :-
  163    (   is_absolute_file_name(RelPath)
  164    ->  Path = RelPath
  165    ;   file_directory_name(RelTo, RelToDir),
  166        directory_file_path(RelToDir, RelPath, Path0),
  167        absolute_file_name(Path0, Path)
  168    ).
  169
  170delete_common_prefix([H|T01], [H|T02], T1, T2) :-
  171    !,
  172    delete_common_prefix(T01, T02, T1, T2).
  173delete_common_prefix(T1, T2, T1, T2).
  174
  175to_dot_dot([], Tail, Tail).
  176to_dot_dot([_], Tail, Tail) :- !.
  177to_dot_dot([_|T0], ['..'|T], Tail) :-
  178    to_dot_dot(T0, T, Tail).
 directory_file_path(+Directory, +File, -Path) is det
directory_file_path(?Directory, ?File, +Path) is det
True when Path is the full path-name for File in Dir. This is comparable to atom_concat(Directory, File, Path), but it ensures there is exactly one / between the two parts. Notes:
  192directory_file_path(Dir, File, Path) :-
  193    nonvar(Dir), nonvar(File),
  194    !,
  195    (   (   is_absolute_file_name(File)
  196        ;   Dir == '.'
  197        )
  198    ->  Path = File
  199    ;   sub_atom(Dir, _, _, 0, /)
  200    ->  atom_concat(Dir, File, Path)
  201    ;   atomic_list_concat([Dir, /, File], Path)
  202    ).
  203directory_file_path(Dir, File, Path) :-
  204    nonvar(Path),
  205    !,
  206    (   nonvar(Dir)
  207    ->  (   Dir == '.',
  208            \+ is_absolute_file_name(Path)
  209        ->  File = Path
  210        ;   sub_atom(Dir, _, _, 0, /)
  211        ->  atom_concat(Dir, File, Path)
  212        ;   atom_concat(Dir, /, TheDir)
  213        ->  atom_concat(TheDir, File, Path)
  214        )
  215    ;   nonvar(File)
  216    ->  atom_concat(Dir0, File, Path),
  217        strip_trailing_slash(Dir0, Dir)
  218    ;   file_directory_name(Path, Dir),
  219        file_base_name(Path, File)
  220    ).
  221directory_file_path(_, _, _) :-
  222    throw(error(instantiation_error(_), _)).
  223
  224strip_trailing_slash(Dir0, Dir) :-
  225    (   atom_concat(D, /, Dir0),
  226        D \== ''
  227    ->  Dir = D
  228    ;   Dir = Dir0
  229    ).
 directory_member(+Directory, -Member, +Options) is nondet
True when Member is a path inside Directory. Options defined are:
recursive(+Boolean)
If true (default false), recurse into subdirectories
follow_links(+Boolean)
If true (default), follow symbolic links.
file_type(+Type)
See absolute_file_name/3.
extensions(+List)
Only return entries whose extension appears in List.
file_errors(+Errors)
How to handle errors. One of fail, warning or error. Default is warning. Errors notably happen if a directory is unreadable or a link points nowhere.
access(+Access)
Only return entries with Access
matches(+GlobPattern)
Only return files that match GlobPattern.
exclude(+GlobPattern)
Exclude files matching GlobPattern.
exclude_directory(+GlobPattern)
Do not recurse into directories matching GlobPattern.
hidden(+Boolean)
If true (default), also return hidden files.

This predicate is safe against cycles introduced by symbolic links to directories.

The idea for a non-deterministic file search predicate comes from Nicos Angelopoulos.

  265directory_member(Directory, Member, Options) :-
  266    dict_create(Dict, options, Options),
  267    (   Dict.get(recursive) == true,
  268        \+ Dict.get(follow_links) == false
  269    ->  empty_nb_set(Visited),
  270        DictOptions = Dict.put(visited, Visited)
  271    ;   DictOptions = Dict
  272    ),
  273    directory_member_dict(Directory, Member, DictOptions).
  274
  275directory_member_dict(Directory, Member, Dict) :-
  276    directory_files(Directory, Files, Dict),
  277    member(Entry, Files),
  278    \+ special(Entry),
  279    directory_file_path(Directory, Entry, AbsEntry),
  280    filter_link(AbsEntry, Dict),
  281    (   exists_directory(AbsEntry)
  282    ->  (   filter_dir_member(AbsEntry, Entry, Dict),
  283            Member = AbsEntry
  284        ;   filter_directory(Entry, Dict),
  285            Dict.get(recursive) == true,
  286            \+ hidden_file(Entry, Dict),
  287            no_link_cycle(AbsEntry, Dict),
  288            directory_member_dict(AbsEntry, Member, Dict)
  289        )
  290    ;   filter_dir_member(AbsEntry, Entry, Dict),
  291        Member = AbsEntry
  292    ).
  293
  294directory_files(Directory, Files, Dict) :-
  295    Errors = Dict.get(file_errors),
  296    !,
  297    errors_directory_files(Errors, Directory, Files).
  298directory_files(Directory, Files, _Dict) :-
  299    errors_directory_files(warning, Directory, Files).
  300
  301errors_directory_files(fail, Directory, Files) :-
  302    catch(directory_files(Directory, Files), _, fail).
  303errors_directory_files(warning, Directory, Files) :-
  304    catch(directory_files(Directory, Files), E,
  305          (   print_message(warning, E),
  306              fail)).
  307errors_directory_files(error, Directory, Files) :-
  308    directory_files(Directory, Files).
  309
  310
  311filter_link(File, Dict) :-
  312    \+ ( Dict.get(follow_links) == false,
  313         read_link(File, _, _)
  314       ).
  315
  316no_link_cycle(Directory, Dict) :-
  317    Visited = Dict.get(visited),
  318    !,
  319    absolute_file_name(Directory, Canonical,
  320                       [ file_type(directory)
  321                       ]),
  322    add_nb_set(Canonical, Visited, true).
  323no_link_cycle(_, _).
  324
  325hidden_file(Entry, Dict) :-
  326    false == Dict.get(hidden),
  327    sub_atom(Entry, 0, _, _, '.').
 filter_dir_member(+Absolute, +BaseName, +Options)
True when the given file satisfies the filter expressions.
  333filter_dir_member(_AbsEntry, Entry, Dict) :-
  334    Exclude = Dict.get(exclude),
  335    wildcard_match(Exclude, Entry),
  336    !, fail.
  337filter_dir_member(_AbsEntry, Entry, Dict) :-
  338    Include = Dict.get(matches),
  339    \+ wildcard_match(Include, Entry),
  340    !, fail.
  341filter_dir_member(AbsEntry, _Entry, Dict) :-
  342    Type = Dict.get(file_type),
  343    \+ matches_type(Type, AbsEntry),
  344    !, fail.
  345filter_dir_member(_AbsEntry, Entry, Dict) :-
  346    ExtList = Dict.get(extensions),
  347    file_name_extension(_, Ext, Entry),
  348    \+ memberchk(Ext, ExtList),
  349    !, fail.
  350filter_dir_member(AbsEntry, _Entry, Dict) :-
  351    Access = Dict.get(access),
  352    \+ access_file(AbsEntry, Access),
  353    !, fail.
  354filter_dir_member(_AbsEntry, Entry, Dict) :-
  355    hidden_file(Entry, Dict),
  356    !, fail.
  357filter_dir_member(_, _, _).
  358
  359matches_type(directory, Entry) :-
  360    !,
  361    exists_directory(Entry).
  362matches_type(Type, Entry) :-
  363    \+ exists_directory(Entry),
  364    user:prolog_file_type(Ext, Type),
  365    file_name_extension(_, Ext, Entry).
 filter_directory(+Entry, +Dict) is semidet
Implement the exclude_directory(+GlobPattern) option.
  372filter_directory(Entry, Dict) :-
  373    Exclude = Dict.get(exclude_directory),
  374    wildcard_match(Exclude, Entry),
  375    !, fail.
  376filter_directory(_, _).
 copy_file(+From, +To) is det
Copy a file into a new file or directory. The data is copied as binary data.
  384copy_file(From, To) :-
  385    destination_file(To, From, Dest),
  386    setup_call_cleanup(
  387        open(Dest, write, Out, [type(binary)]),
  388        copy_from(From, Out),
  389        close(Out)).
  390
  391copy_from(File, Stream) :-
  392    setup_call_cleanup(
  393        open(File, read, In, [type(binary)]),
  394        copy_stream_data(In, Stream),
  395        close(In)).
  396
  397destination_file(Dir, File, Dest) :-
  398    exists_directory(Dir),
  399    !,
  400    file_base_name(File, Base),
  401    directory_file_path(Dir, Base, Dest).
  402destination_file(Dest, _, Dest).
 make_directory_path(+Dir) is det
Create Dir and all required components (like mkdir -p). Can raise various file-specific exceptions.
  410make_directory_path(Dir) :-
  411    make_directory_path_2(Dir),
  412    !.
  413make_directory_path(Dir) :-
  414    permission_error(create, directory, Dir).
  415
  416make_directory_path_2(Dir) :-
  417    exists_directory(Dir),
  418    !.
  419make_directory_path_2(Dir) :-
  420    atom_concat(RealDir, '/', Dir),
  421    RealDir \== '',
  422    !,
  423    make_directory_path_2(RealDir).
  424make_directory_path_2(Dir) :-
  425    Dir \== (/),
  426    !,
  427    file_directory_name(Dir, Parent),
  428    make_directory_path_2(Parent),
  429    E = error(existence_error(directory, _), _),
  430    catch(make_directory(Dir), E,
  431          (   exists_directory(Dir)
  432          ->  true
  433          ;   throw(E)
  434          )).
 copy_directory(+From, +To) is det
Copy the contents of the directory From to To (recursively). If To is the name of an existing directory, the contents of From are copied into To. I.e., no subdirectory using the basename of From is created.
  443copy_directory(From, To) :-
  444    (   exists_directory(To)
  445    ->  true
  446    ;   make_directory(To)
  447    ),
  448    directory_files(From, Entries),
  449    maplist(copy_directory_content(From, To), Entries).
  450
  451copy_directory_content(_From, _To, Special) :-
  452    special(Special),
  453    !.
  454copy_directory_content(From, To, Entry) :-
  455    directory_file_path(From, Entry, Source),
  456    directory_file_path(To, Entry, Dest),
  457    (   exists_directory(Source)
  458    ->  copy_directory(Source, Dest)
  459    ;   copy_file(Source, Dest)
  460    ).
  461
  462special(.).
  463special(..).
 delete_directory_and_contents(+Dir) is det
Recursively remove the directory Dir and its contents. If Dir is a symbolic link or symbolic links inside Dir are encountered, the links are removed rather than their content. Use with care!
  471delete_directory_and_contents(Dir) :-
  472    read_link(Dir, _, _),
  473    !,
  474    delete_file(Dir).
  475delete_directory_and_contents(Dir) :-
  476    directory_files(Dir, Files),
  477    maplist(delete_directory_contents(Dir), Files),
  478    E = error(existence_error(directory, _), _),
  479    catch(delete_directory(Dir), E,
  480          (   \+ exists_directory(Dir)
  481          ->  true
  482          ;   throw(E)
  483          )).
  484
  485delete_directory_contents(_, Entry) :-
  486    special(Entry),
  487    !.
  488delete_directory_contents(Dir, Entry) :-
  489    directory_file_path(Dir, Entry, Delete),
  490    (   exists_directory(Delete)
  491    ->  delete_directory_and_contents(Delete)
  492    ;   E = error(existence_error(file, _), _),
  493        catch(delete_file(Delete), E,
  494              (   \+ exists_file(Delete)
  495              ->  true
  496              ;   throw(E)))
  497    ).
 delete_directory_contents(+Dir) is det
Remove all content from directory Dir, without removing Dir itself. Similar to delete_directory_and_contents/2, if symbolic links are encountered in Dir, the links are removed rather than their content.
  506delete_directory_contents(Dir) :-
  507    directory_files(Dir, Files),
  508    maplist(delete_directory_contents(Dir), Files).
 chmod(+File, +Spec) is det
Set the mode of the target file. Spec is one of +Mode, -Mode or a plain Mode, which adds new permissions, revokes permissions or sets the exact permissions. Mode itself is an integer, a POSIX mode name or a list of POSIX mode names. Defines names are suid, sgid, svtx and all names defined by the regular expression [ugo]*[rwx]*. Specifying none of "ugo" is the same as specifying all of them. For example, to make a file executable for the owner (user) and group, we can use:
?- chmod(myfile, +ugx).
  526chmod(File, +Spec) :-
  527    must_be(ground, Spec),
  528    !,
  529    mode_bits(Spec, Bits),
  530    file_mode_(File, Mode0),
  531    Mode is Mode0 \/ Bits,
  532    chmod_(File, Mode).
  533chmod(File, -Spec) :-
  534    must_be(ground, Spec),
  535    !,
  536    mode_bits(Spec, Bits),
  537    file_mode_(File, Mode0),
  538    Mode is Mode0 /\ \Bits,
  539    chmod_(File, Mode).
  540chmod(File, Spec) :-
  541    must_be(ground, Spec),
  542    !,
  543    mode_bits(Spec, Bits),
  544    chmod_(File, Bits).
  545
  546mode_bits(Spec, Spec) :-
  547    integer(Spec),
  548    !.
  549mode_bits(Name, Bits) :-
  550    atom(Name),
  551    !,
  552    (   file_mode(Name, Bits)
  553    ->  true
  554    ;   domain_error(posix_file_mode, Name)
  555    ).
  556mode_bits(Spec, Bits) :-
  557    must_be(list(atom), Spec),
  558    phrase(mode_bits(0, Bits), Spec).
  559
  560mode_bits(Bits0, Bits) -->
  561    [Spec], !,
  562    (   { file_mode(Spec, B), Bits1 is Bits0\/B }
  563    ->  mode_bits(Bits1, Bits)
  564    ;   { domain_error(posix_file_mode, Spec) }
  565    ).
  566mode_bits(Bits, Bits) -->
  567    [].
  568
  569file_mode(suid, 0o4000).
  570file_mode(sgid, 0o2000).
  571file_mode(svtx, 0o1000).
  572file_mode(Name, Bits) :-
  573    atom_chars(Name, Chars),
  574    phrase(who_mask(0, WMask0), Chars, Rest),
  575    (   WMask0 =:= 0
  576    ->  WMask = 0o0777
  577    ;   WMask = WMask0
  578    ),
  579    maplist(mode_char, Rest, MBits),
  580    foldl(or, MBits, 0, Mask),
  581    Bits is Mask /\ WMask.
  582
  583who_mask(M0, M) -->
  584    [C],
  585    { who_mask(C,M1), !,
  586      M2 is M0\/M1
  587    },
  588    who_mask(M2,M).
  589who_mask(M, M) -->
  590    [].
  591
  592who_mask(o, 0o0007).
  593who_mask(g, 0o0070).
  594who_mask(u, 0o0700).
  595
  596mode_char(r, 0o0444).
  597mode_char(w, 0o0222).
  598mode_char(x, 0o0111).
  599
  600or(B1, B2, B) :-
  601    B is B1\/B2