1/* Part of SWI-Prolog 2 3 WWW: http://www.swi-prolog.org 4 Copyright (c) 2021, SWI-Prolog Solutions b.v. 5 All rights reserved. 6 7 Redistribution and use in source and binary forms, with or without 8 modification, are permitted provided that the following conditions 9 are met: 10 11 1. Redistributions of source code must retain the above copyright 12 notice, this list of conditions and the following disclaimer. 13 14 2. Redistributions in binary form must reproduce the above copyright 15 notice, this list of conditions and the following disclaimer in 16 the documentation and/or other materials provided with the 17 distribution. 18 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 POSSIBILITY OF SUCH DAMAGE. 31*/ 32 33:- module(file_systems, 34 [ rename_file/2, % +OldName, +NewName 35 rename_directory/2, % +OldName, +NewName 36 37 delete_file/1, % +OldName 38 delete_directory/1, % +Directory 39 delete_directory/2, % +Directory, +Options 40 41 directory_exists/1, % +Directory 42 directory_exists/2, % +Directory, +Mode 43 44 make_directory/1, % +Directory 45 46 file_exists/1, % +File 47 file_exists/2, % +File, +Mode 48 file_must_exist/1, % +File 49 file_must_exist/2, % +File, +Mode 50 directory_must_exist/1, % +File 51 directory_must_exist/2, % +File, +Mode 52 53 directory_member_of_directory/2, % -BaseName, -FullName 54 directory_member_of_directory/3, % +Directory, -BaseName, -FullName 55 directory_member_of_directory/4, % +Directory, +Pattern, -BaseName, -FullName 56 file_member_of_directory/2, % -BaseName, -FullName 57 file_member_of_directory/3, % +Directory, -BaseName, -FullName 58 file_member_of_directory/4, % +Directory, +Pattern, -BaseName, -FullName 59 directory_members_of_directory/1, % -Set 60 directory_members_of_directory/2, % +Directory, -Set 61 directory_members_of_directory/3, % +Directory, +Pattern, -Set 62 file_members_of_directory/1, % -Set 63 file_members_of_directory/2, % +Directory, -Set 64 file_members_of_directory/3, % +Directory, +Pattern, -Set 65 66 directory_property/2, % +Directory, ?Property 67 directory_property/3, % +Directory, ?Property, ?Value 68 file_property/2, % +File, ?Property 69 file_property/3, % +File, ?Property, ?Value 70 71 current_directory/1, % -Directory 72 current_directory/2 % -Directory, +NewDirectory 73 ]). 74:- use_module(library(filesex), [delete_directory_and_contents/1, directory_member/3, set_time_file/3]). 75:- use_module(library(lists), [member/2]). 76:- use_module(system, [datime/2]). 77 78/** <module> SICStus 4 library(file_systems). 79 80@tbd This library is incomplete. 81 As of SICStus 4.6.0, the following predicates are missing: 82 83 * close_all_streams/0 84 85 Some predicates don't fully support all options available on SICStus. 86 See the documentation for individual predicates for details. 87 88 The file access modes `execute` and `search` are interpreted 89 slightly differently on SICStus and SWI. On SWI, `execute` and 90 `search` are equivalent - both can be used with regular files 91 and directories and will check execute or search permission 92 depending on the file type, not the mode atom. 93 94 SICStus on the other hand checks the access modes only if the 95 file in question has the appropriate type. Checking access mode 96 `execute` on a directory or `search` on a regular file is 97 equivalent to checking `exist`. 98 99 This difference affects not just file_exists/2 and 100 directory_exists/2 in this library, but also the 101 built-in absolute_file_name/3 with the option 102 access(Mode). 103 104 On the other hand, file_property/2 and 105 directory_property/2 with properties `executable` and 106 `searchable` are *not* affected - here the emulation 107 matches the native SICStus behavior. 108 109@see https://sicstus.sics.se/sicstus/docs/4.6.0/html/sicstus.html/lib_002dfile_005fsystems.html 110*/ 111 112% Note: Unlike most of SWI's built-in file system predicates, 113% SICStus library(file_systems) draws a strict distinction between files and directories. 114% This means that "file" predicates will operate only on regular files - 115% directories must be manipulated using the corresponding "directory" predicates instead. 116% Because of this, 117% some of SWI's built-in file predicates 118% (that can operate on both files and directories) 119% need to be replaced with versions that throw an error when receiving a non-regular file. 120 121 122%! rename_file(+OldName, +NewName) is det. 123% 124% Like SWI's built-in rename_file/2, but only works on regular 125% files. To rename directories, rename_directory/2 must be used. 126 127rename_file(OldName, NewName) :- 128 file_must_exist(OldName), 129 system:rename_file(OldName, NewName). 130 131%! rename_directory(+OldName, +NewName) is det. 132% 133% Like SWI's built-in rename_file/2, but only works on 134% directories. To rename regular files, rename_file/2 must be used. 135 136rename_directory(OldName, NewName) :- 137 directory_must_exist(OldName), 138 system:rename_file(OldName, NewName). 139 140 141%! delete_file(+OldName) is det. 142% 143% Like SWI's built-in delete_file/1, but only works on regular 144% files. To delete directories, delete_directory/1 must be used. 145 146delete_file(OldName) :- 147 file_must_exist(OldName), 148 system:delete_file(OldName). 149 150% SWI's built-in delete_directory/1 behaves like the one from SICStus library(file_systems). 151 152directory_not_empty_error(error(permission_error(delete, directory, _), _)). 153 154%! delete_directory(+OldName, +Options) is semidet. 155% 156% Extended verison of delete_directory/1. The only available 157% option is `if_nonempty(Value)`, which controls the behavior 158% when OldName is not empty. Value may be `ignore` (silently 159% succeed without deleting anything), `fail` (silently fail 160% without deleting anything), `error` (throw an error - default 161% behavior), and `delete` (recursively delete the directory and 162% its contents, as if by delete_directory_and_contents/1 from 163% library(filesex)). 164 165delete_directory(OldName, []) :- !, delete_directory(OldName). 166delete_directory(OldName, [if_nonempty(ignore)]) :- !, 167 catch(delete_directory(OldName), E, 168 (directory_not_empty_error(E) -> true ; throw(E))). 169delete_directory(OldName, [if_nonempty(fail)]) :- !, 170 catch(delete_directory(OldName), E, 171 (directory_not_empty_error(E) -> fail ; throw(E))). 172delete_directory(OldName, [if_nonempty(error)]) :- !, delete_directory(OldName). 173delete_directory(OldName, [if_nonempty(delete)]) :- !, delete_directory_and_contents(OldName). 174 175 176%! directory_exists(+Directory) is semidet. 177%! directory_exists(+Directory, +Mode) is semidet. 178% 179% True if a directory exists at path Directory and can be accessed 180% according to Mode (defaults to `exist`). Accepts the same access 181% modes as absolute_file_name/3's `access` option. 182 183directory_exists(Directory) :- exists_directory(Directory). 184directory_exists(Directory, Mode) :- 185 % According to the SICStus 4.6 docs, this is "more or less equivalent". 186 absolute_file_name(Directory, _, [file_type(directory), access(Mode), file_errors(fail)]). 187 188% SWI's built-in make_directory/1 behaves like the one from SICStus library(file_systems). 189 190%! file_exists(+File) is semidet. 191%! file_exists(+File, +Mode) is semidet. 192% 193% True if a regular file exists at path File and can be accessed 194% according to Mode (defaults to `exist`). Accepts the same access 195% modes as absolute_file_name/3's `access` option. 196 197file_exists(File) :- exists_file(File). 198file_exists(File, Mode) :- 199 % According to the SICStus 4.6 docs, this is "more or less equivalent". 200 absolute_file_name(File, _, [access(Mode), file_errors(fail)]). 201 202%! file_must_exist(+File) is det. 203%! file_must_exist(+File, +Mode) is det. 204% 205% Ensure that a regular file exists at path File and can be 206% accessed according to Mode (defaults to `exist`). Otherwise an 207% exception is thrown. Accepts the same access modes as 208% absolute_file_name/3's `access` option. 209 210file_must_exist(File) :- file_must_exist(File, exist). 211file_must_exist(File, Mode) :- 212 % According to the SICStus 4.6 docs, this is "more or less equivalent". 213 absolute_file_name(File, _, [access(Mode), file_errors(error)]). 214 215%! directory_must_exist(+Directory) is det. 216%! directory_must_exist(+Directory, +Mode) is det. 217% 218% Ensure that a directory exists at path Directory and can be 219% accessed according to Mode (defaults to `exist`). Otherwise an 220% exception is thrown. Accepts the same access modes as 221% absolute_file_name/3's `access` option. 222 223directory_must_exist(Directory) :- directory_must_exist(Directory, exist). 224directory_must_exist(Directory, Mode) :- 225 % According to the SICStus 4.6 docs, this is "more or less equivalent". 226 absolute_file_name(Directory, _, [file_type(directory), access(Mode), file_errors(error)]). 227 228 229member_of_directory_internal(Directory, BaseName, FullName, Options) :- 230 absolute_file_name(Directory, AbsDirectory, [file_type(directory)]), 231 directory_member(AbsDirectory, FullName, Options), 232 file_base_name(FullName, BaseName). 233 234%! directory_member_of_directory(-BaseName, -FullName) is nondet. 235%! directory_member_of_directory(+Directory, -BaseName, -FullName) is nondet. 236%! directory_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet. 237%! file_member_of_directory(-BaseName, -FullName) is nondet. 238%! file_member_of_directory(+Directory, -BaseName, -FullName) is nondet. 239%! file_member_of_directory(+Directory, +Pattern, -BaseName, -FullName) is nondet. 240% 241% True if Directory contains a directory or regular file 242% (respectively) named BaseName and the file's absolute path is 243% FullName. If Directory is not given, it defaults to the current 244% working directory. If Pattern is given, only succeeds if 245% BaseName also matches that glob pattern. 246% 247% These predicates enumerate all matching files on backtracking. 248% This is also the intended usage pattern. For checking if a 249% specific file/directory exists, or to get its absolute path, 250% it's better to use file_exists/1, directory_exists/1, or 251% absolute_file_name/3. 252 253directory_member_of_directory(BaseName, FullName) :- 254 directory_member_of_directory((.), BaseName, FullName). 255directory_member_of_directory(Directory, BaseName, FullName) :- 256 member_of_directory_internal(Directory, BaseName, FullName, [file_type(directory)]). 257directory_member_of_directory(Directory, Pattern, BaseName, FullName) :- 258 member_of_directory_internal(Directory, BaseName, FullName, [file_type(directory), matches(Pattern)]). 259 260file_member_of_directory(BaseName, FullName) :- 261 file_member_of_directory((.), BaseName, FullName). 262file_member_of_directory(Directory, BaseName, FullName) :- 263 member_of_directory_internal(Directory, BaseName, FullName, []), 264 % directory_member/3 has no option for filtering out directories... 265 \+ directory_exists(FullName). 266file_member_of_directory(Directory, Pattern, BaseName, FullName) :- 267 member_of_directory_internal(Directory, BaseName, FullName, [matches(Pattern)]), 268 % directory_member/3 has no option for filtering out directories... 269 \+ directory_exists(FullName). 270 271%! directory_members_of_directory(-Set) is det. 272%! directory_members_of_directory(+Directory, -Set) is det. 273%! directory_members_of_directory(+Directory, +Pattern, -Set) is det. 274%! file_members_of_directory(-Set) is det. 275%! file_members_of_directory(+Directory, -Set) is det. 276%! file_members_of_directory(+Directory, +Pattern, -Set) is det. 277% 278% Unifies Set with a set of BaseName-FullName entries for all 279% directories or regular files (respectively) in Directory. If 280% Directory is not given, it defaults to the current working 281% directory. If Pattern is given, Set only includes entries where 282% BaseName matches that glob pattern. 283 284directory_members_of_directory(Set) :- 285 directory_members_of_directory((.), Set). 286directory_members_of_directory(Directory, Set) :- 287 findall(BaseName-FullName, directory_member_of_directory(Directory, BaseName, FullName), Set). 288directory_members_of_directory(Directory, Pattern, Set) :- 289 findall(BaseName-FullName, directory_member_of_directory(Directory, Pattern, BaseName, FullName), Set). 290 291file_members_of_directory(Set) :- 292 file_members_of_directory((.), Set). 293file_members_of_directory(Directory, Set) :- 294 findall(BaseName-FullName, file_member_of_directory(Directory, BaseName, FullName), Set). 295file_members_of_directory(Directory, Pattern, Set) :- 296 findall(BaseName-FullName, file_member_of_directory(Directory, Pattern, BaseName, FullName), Set). 297 298 299%! file_property(+Path, ?Property) is semidet. 300%! file_property(+Path, ?Property, -Value) is semidet. 301%! directory_property(+Path, ?Property) is semidet. 302%! directory_property(+Path, ?Property, -Value) is semidet. 303% 304% True if a regular file or directory (respectively) exists at 305% Path and it has the given property and value. Property may be 306% unbound to backtrack over all available properties. If the Value 307% parameter is omitted, succeeds if Property has value `true`. 308% 309% The following properties are currently supported: 310% 311% * create_timestamp 312% * modify_timestamp 313% * access_timestamp 314% The file/directory's creation/modification/access time as a Unix 315% timestamp (as returned by SWI's set_time_file/3). 316% * create_localtime 317% * modify_localtime 318% * access_localtime 319% The file/directory's creation/modification/access time as a 320% datime/6 term (as returned by datime/2 from SICStus 321% library(system)). 322% * readable 323% * writable 324% * executable 325% * searchable 326% `true` or `false` depending on whether the file/directory is 327% readable/writable/executable/searchable. `executable` is only 328% supported on regular files and `searchable` only on directories. 329% * size_in_bytes 330% The file's size in bytes. Not supported on directories. 331% 332% On Unix systems, `create_timestamp`/`create_localtime` don't 333% return the file's actual creation time, but rather its "ctime" 334% or "metadata change time". This matches the behavior of 335% SICStus 4.6.0. 336% 337% As of SICStus 4.6.0, the following properties are not yet 338% emulated: 339% 340% * set_user_id 341% * set_group_id 342% * save_text 343% * who_can_read 344% * who_can_write 345% * who_can_execute 346% * who_can_search 347% * owner_user_id 348% * owner_group_id 349% * owner_user_name 350% * owner_group_name 351 352time_property_name(create_timestamp). 353time_property_name(modify_timestamp). 354time_property_name(access_timestamp). 355time_property_name(create_localtime). 356time_property_name(modify_localtime). 357time_property_name(access_localtime). 358 359time_property(create_timestamp, Ctime, _, _, Timestamp) :- 360 Timestamp is integer(Ctime). 361time_property(modify_timestamp, _, Mtime, _, Timestamp) :- 362 Timestamp is integer(Mtime). 363time_property(access_timestamp, _, _, Atime, Timestamp) :- 364 Timestamp is integer(Atime). 365time_property(create_localtime, Ctime, _, _, Datime) :- 366 datime(Ctime, Datime). 367time_property(modify_localtime, _, Mtime, _, Datime) :- 368 datime(Mtime, Datime). 369time_property(access_localtime, _, _, Atime, Datime) :- 370 datime(Atime, Datime). 371 372% Properties that are available and implemented identically for files and directories. 373file_or_directory_property(FileOrDirectory, Name, Value) :- 374 \+ \+ time_property_name(Name), 375 % When backtracking over time properties, 376 % call set_time_file once and reuse the values for all properties, 377 % so that the different times and timestamp/localtime are consistent with each other. 378 set_time_file(FileOrDirectory, [changed(Ctime), modified(Mtime), access(Atime)], []), 379 time_property(Name, Ctime, Mtime, Atime, Value). 380 381directory_property(Directory, Property) :- directory_property(Directory, Property, true). 382 383directory_property(Directory, readable, Value) :- 384 directory_exists(Directory, read) -> Value = true ; Value = false. 385directory_property(Directory, writable, Value) :- 386 directory_exists(Directory, write) -> Value = true ; Value = false. 387directory_property(Directory, searchable, Value) :- 388 directory_exists(Directory, search) -> Value = true ; Value = false. 389directory_property(Directory, Property, Value) :- 390 file_or_directory_property(Directory, Property, Value). 391 392file_property(File, Property) :- file_property(File, Property, true). 393 394file_property(File, readable, Value) :- 395 file_exists(File, read) -> Value = true ; Value = false. 396file_property(File, writable, Value) :- 397 file_exists(File, write) -> Value = true ; Value = false. 398file_property(File, executable, Value) :- 399 file_exists(File, execute) -> Value = true ; Value = false. 400file_property(File, size_in_bytes, Value) :- size_file(File, Value). 401file_property(File, Property, Value) :- 402 file_or_directory_property(File, Property, Value). 403 404 405%! current_directory(-Directory) is det. 406%! current_directory(-Directory, +NewDirectory) is det. 407% 408% Unifies Directory with the current working directory path. 409% In the 2-argument form, also changes the working directory to 410% the path NewDirectory. 411 412current_directory(Directory) :- working_directory(Directory, Directory). 413current_directory(Directory, NewDirectory) :- working_directory(Directory, NewDirectory)