/* Part of SWI-Prolog Author: Jan Wielemaker E-mail: J.Wielemaker@cs.vu.nl WWW: http://www.swi-prolog.org Copyright (C): 2009-2019, VU University Amsterdam CWI, Amsterdam This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA As a special exception, if you link this library with other files, compiled with a Free Software compiler, to produce an executable, this library does not by itself cause the resulting executable to be covered by the GNU General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU General Public License. */ :- module(plweb_download, []). :- use_module(library(http/html_write)). :- use_module(library(http/js_write)). :- use_module(library(http/http_dispatch)). :- use_module(library(http/http_path)). :- use_module(library(http/http_parameters)). :- use_module(library(http/http_dirindex)). :- use_module(library(http/http_wrapper)). :- use_module(library(dcg/basics)). :- use_module(library(broadcast)). :- use_module(library(pairs)). :- use_module(library(lists)). :- use_module(library(apply)). :- use_module(library(error)). :- use_module(library(filesex)). :- use_module(library(persistency)). :- use_module(library(crypto)). :- use_module(library(random)). :- use_module(wiki). %% download(+Request) is det. % % HTTP handler for SWI-Prolog download pages. :- http_handler(download(devel), download_table, []). :- http_handler(download(stable), download_table, []). :- http_handler(download(old), download_table, []). :- http_handler(download('daily/bin/'), download_daily, []). :- http_handler(download(.), download, [prefix, spawn(download), priority(10)]). :- http_handler(root(download), http_redirect(moved, download(.)), [priority(10)]). %% download_table(+Request) % % Provide a table with possible download targets. % test edit download_table(Request) :- http_parameters(Request, [ show(Show, [oneof([all,latest]), default(latest)]) ]), memberchk(path(Path), Request), http_absolute_location(root(download), DownLoadRoot, []), atom_concat(DownLoadRoot, DownLoadDir, Path), absolute_file_name(download(DownLoadDir), Dir, [ file_type(directory), access(read) ]), list_downloads(Dir, [show(Show), request(Request)]). %% list_downloads(+Directory) list_downloads(Dir, Options) :- ( wiki_file_to_dom(Dir, 'header.txt', Header0) -> ( Header0 = [h1(_, Title)|Header] -> true ; Header = Header0 ) ; Header = [] ), ( var(Title) -> Title = 'SWI-Prolog downloads' ; true ), reply_html_page( download(Dir, Title), title('SWI-Prolog downloads'), [ \html(Header), br(clear(all)), table(class(downloads), \download_table(Dir, Options)), \machine_download_text, \wiki(Dir, 'footer.txt') ]). wiki_file_to_dom(Dir, File, DOM) :- directory_file_path(Dir, File, WikiFile), access_file(WikiFile, read), !, wiki_file_to_dom(WikiFile, DOM). wiki(Dir, File) --> { wiki_file_to_dom(Dir, File, DOM) }, !, html(DOM). wiki(_, _) --> []. machine_download_text --> html({|html||
Install scripts may download the SHA256 checksum by appending .sha256 to the file name. Scripts can download the latest version by replacing the version of the file with latest. This causes the server to reply with the location of the latest version using an HTTP 303 See Other message.
|}). download_table(Dir, Options) --> list_files(Dir, bin, bin, 'Binaries', Options), list_files(Dir, src, src, 'Sources', Options), list_files(Dir, doc, doc, 'Documentation', Options), toggle_show(Options). %% toggle_show(+Options) is det. % % Add a toggle to switch between showing only the latest version % and all versions. toggle_show(Options) --> { option(request(Request), Options), memberchk(path(Path), Request), !, file_base_name(Path, MySelf), ( option(show(all), Options) -> NewShow = latest ; NewShow = all ) }, html(tr(td([class(toggle), colspan(3)], a(href(MySelf+'?show='+NewShow), [ 'Show ', NewShow, ' files' ])))). toggle_show(_) --> []. %% list_files(+Dir, +SubDir, +Class, +Label, +Options) is det. % % Create table rows for all files in Dir/SubDir. If files are % present, emit a =tr= with Label and a =tr= row for each each % matching file. Options are: % % * show(Show) % One of =all= or =latest= (default). list_files(Dir, SubDir, Class, Label, Options) --> { directory_file_path(Dir, SubDir, Directory), download_files(Directory, Class, Files, Options), Files \== [] }, html(tr(th(colspan(3), Label))), list_files(Files). list_files(_, _, _, _, _) --> []. list_files([]) --> []. list_files([H|T]) --> list_file(H), list_files(T). list_file(File) --> html(tr(class(download), [ td(class(dl_icon), \file_icon(File)), td(class(dl_size), \file_size(File)), td(class(dl_file), \file_description(File)) ])). file_icon(file(Type, PlatForm, _, _, _)) --> { icon_for_file(Type, PlatForm, Icon, Alt), !, http_absolute_location(icons(Icon), HREF, []) }, html(img([src(HREF), alt(Alt)])). file_icon(_) --> html(?). % no defined icon icon_for_file(bin, linux(universal), 'linux.png', 'Linux 32/64 intel'). icon_for_file(bin, linux(_,_), 'linux32.gif', 'Linux RPM'). icon_for_file(bin, macos(lion,_), 'lion.png', 'Lion'). icon_for_file(bin, macos(snow_leopard,_), 'snowleopard.gif', 'Snow Leopard'). icon_for_file(bin, macos(snow_leopard_and_later,_), 'macapp.png', 'Snow Leopard and later'). icon_for_file(bin, macos(bundle,_), 'macapp.png', 'MacOS bundle'). icon_for_file(bin, macos(_,_), 'mac.gif', 'MacOSX version'). icon_for_file(_, windows(win32), 'win32.gif', 'Windows version (32-bits)'). icon_for_file(_, windows(win64), 'win64.gif', 'Windows version (64-bits)'). icon_for_file(src, _, 'src.gif', 'Source archive'). icon_for_file(_, pdf, 'pdf.gif', 'PDF file'). file_size(file(_, _, _, _, Path)) --> { size_file(Path, Bytes) }, html('~D bytes'-[Bytes]). file_description(file(bin, PlatForm, Version, _, Path)) --> { down_file_href(Path, HREF) }, html([ a(href(HREF), [ 'SWI-Prolog ', \version(Version), ' for ', \platform(PlatForm) ]), \platform_notes(PlatForm, Path), \checksum(Path) ]). file_description(file(src, Format, Version, _, Path)) --> { down_file_href(Path, HREF) }, html([ a(href(HREF), [ 'SWI-Prolog source for ', \version(Version) ]), \platform_notes(Format, Path), \checksum(Path) ]). file_description(file(doc, Format, Version, _, Path)) --> { down_file_href(Path, HREF) }, html([ a(href(HREF), [ 'SWI-Prolog ', \version(Version), ' reference manual in PDF' ]), \platform_notes(Format, Path) ]). file_description(file(pkg(Pkg), PlatForm, Version, _, Path)) --> { down_file_href(Path, HREF) }, html([ a(href(HREF), [ \package(Pkg), ' (version ', \version(Version), ') for ', \platform(PlatForm) ]), \platform_notes(pkg(Pkg), Path) ]). package(Name) --> html([ 'Package ', Name ]). version(version(Major, Minor, Patch, '')) --> !, html(b('~w.~w.~w'-[Major, Minor, Patch])). version(version(Major, Minor, Patch, Tag)) --> html(b('~w.~w.~w-~w'-[Major, Minor, Patch, Tag])). checksum(Path) --> { file_checksum(Path, SHA256) }, html(div([ class(checksum), title('You can use the checksum to verify the integrity \c of the downloaded file. It provides some protection \c against deliberate tamporing with the file.') ], [ span(class('checkum-header'), 'SHA256'), :, span(class([checksum,sha256]), SHA256) ])). down_file_href(Path, HREF) :- absolute_file_name(download(.), Dir, [ file_type(directory), access(read) ]), atom_concat(Dir, SlashLocal, Path), delete_leading_slash(SlashLocal, Local), add_envelope(Local, SafeLocal), http_absolute_location(download(SafeLocal), HREF, []). delete_leading_slash(SlashPath, Path) :- atom_concat(/, Path, SlashPath), !. delete_leading_slash(Path, Path). platform(linux(universal)) --> html(['Linux 32/64 bits (TAR)']). platform(linux(rpm, _)) --> html(['i586/Linux (RPM)']). platform(macos(Name, CPU)) --> html(['MacOSX ', \html_macos_version(Name), ' on ', b(CPU)]). platform(windows(win32)) --> html(['Microsoft Windows (32 bit)']). platform(windows(win64)) --> html(['Microsoft Windows (64 bit)']). html_macos_version(tiger) --> html('10.4 (Tiger)'). html_macos_version(leopard) --> html('10.5 (Leopard)'). html_macos_version(snow_leopard) --> html('10.6 (Snow Leopard)'). html_macos_version(lion) --> html('10.7 (Lion)'). html_macos_version(snow_leopard_and_later) --> html('10.6 (Snow Leopard) and later'). html_macos_version(bundle) --> html('10.12 (Sierra) and later'). html_macos_version(OS) --> html(OS). %% platform_notes(+Platform, +Path) is det. % % Include notes on the platform. These notes are stored in a wiki % file in the same directory as the download file. platform_notes(Platform, Path) --> { file_directory_name(Path, Dir), platform_note_file(Platform, File), atomic_list_concat([Dir, /, File], NoteFile), debug(download, 'Trying note-file ~q', [NoteFile]), access_file(NoteFile, read), !, debug(download, 'Found note-file ~q', [NoteFile]), wiki_file_to_dom(NoteFile, DOM) }, html(DOM). platform_notes(_, _) --> []. platform_note_file(linux(rpm,_), 'linux-rpm.txt'). platform_note_file(linux(universal), 'linux.txt'). platform_note_file(windows(win32), 'win32.txt'). platform_note_file(windows(win64), 'win64.txt'). platform_note_file(pkg(Pkg), File) :- file_name_extension(Pkg, txt, File). platform_note_file(macos(Version,_), File) :- atomic_list_concat([macosx, -, Version, '.txt'], File). platform_note_file(macos(_,_), 'macosx.txt'). platform_note_file(tgz, 'src-tgz.txt'). platform_note_file(pdf, 'doc-pdf.txt'). /******************************* * CLASSIFY FILES * *******************************/ %% download_files(+Dir, +Class, -Files, +Options) % % Files is a list of files that satisfy Class and Options in % Dir/Subdir. :- dynamic download_cache/6. % Hash, Dir, Class, Opts, Time, Files download_files(Dir, Class, Files, Options0) :- exists_directory(Dir), !, include(download_option, Options0, Options), term_hash(ci(Dir,Class,Options), Hash), time_file(Dir, DirTime), ( download_cache(Hash, Dir, Class, Options, Time, Files0), ( DirTime == Time -> true ; retractall(download_cache(Hash, Dir, Class, Options, _, _)), fail ) -> true ; download_files_nc(Dir, Class, Files0, Options), asserta(download_cache(Hash, Dir, Class, Options, DirTime, Files0)) ), Files = Files0. download_files(_, _, [], _). clear_download_cache :- retractall(download_cache(_Hash, _Dir, _Class, _Options, _Time, _Files0)). download_option(show(_)). download_files_nc(Directory, Class, Sorted, Options) :- atom_concat(Directory, '/*', Pattern), expand_file_name(Pattern, Files), classify_files(Files, Class, Classified, Options), sort_files(Classified, Sorted, Options). classify_files([], _, [], _). classify_files([H0|T0], Class, [H|T], Options) :- classify_file(H0, H, Options), arg(1, H, Classification), subsumes_term(Class, Classification), !, classify_files(T0, Class, T, Options). classify_files([_|T0], Class, T, Options) :- classify_files(T0, Class, T, Options). %% classify_file(+Path, -Term, +Options) is semidet. classify_file(Path, file(Type, Platform, Version, Name, Path), Options) :- file_base_name(Path, Name), atom_codes(Name, Codes), phrase(file(Type, Platform, Version, Options), Codes). file(bin, macos(OSVersion, CPU), Version, Options) --> { option(show(all), Options) }, "swi-prolog-", opt_devel, long_version(Version), "-", macos_version(OSVersion), ( "-", macos_cpu(CPU) -> "" ; { macos_def_cpu(OSVersion, CPU) } ), ".mpkg.zip", !. % Cmake version file(bin, macos(bundle, intel), Version, _) --> "swipl-", long_version(Version), opt_release(_), opt_cpu(_), ".dmg", !. file(bin, macos(snow_leopard_and_later, intel), Version, _) --> "SWI-Prolog-", long_version(Version), ".dmg", !. file(bin, windows(WinType), Version, _) --> "swipl-", long_version(Version), opt_release(_), cmake_win_type(WinType), ".exe", !. file(bin, windows(WinType), Version, _) --> win_type(WinType), "pl", short_version(Version), ".exe", !. file(bin, windows(WinType), Version, _) --> swipl, win_type(WinType), "-", short_version(Version), ".exe", !. file(bin, linux(rpm, suse), Version, _) --> swipl, long_version(Version), "-", digits(_Build), ".i586.rpm", !. file(bin, linux(universal), Version, _) --> "swipl-", long_version(Version), "-", "linux", ".tar.gz", !. file(src, tgz, Version, _) --> swipl, long_version(Version), ".tar.gz", !. file(doc, pdf, Version, _) --> "SWI-Prolog-", long_version(Version), ".pdf", !. swipl --> "swipl-", !. swipl --> "pl-". opt_release(Rel) --> "-", int(Rel, 4), !. opt_release(-) --> "". opt_devel --> "devel-", !. opt_devel --> "". opt_cpu(x86_64) --> ".", "x86_64", !. opt_cpu(unknown) --> "". macos_version(tiger) --> "tiger". macos_version(leopard) --> "leopard". macos_version(snow_leopard) --> "snow-leopard". macos_version(lion) --> "lion". macos_cpu(ppc) --> "powerpc". macos_cpu(intel) --> "intel". macos_cpu(x86) --> "32bit". macos_def_cpu(snow_leopard, intel) :- !. macos_def_cpu(lion, intel) :- !. macos_def_cpu(_, ppc). win_type(win32) --> "w32". win_type(win64) --> "w64". cmake_win_type(win64) --> ".", "x64". cmake_win_type(win32) --> ".", "x86". long_version(version(Major, Minor, Patch, Tag)) --> int(Major, 1), ".", int(Minor, 2), ".", int(Patch, 2), !, tag(Tag), !. long_version(latest) --> "latest". tag(Tag) --> "-", alnums(Codes), !, { atom_codes(Tag, Codes) }. tag('') --> "". int(Value, MaxDigits) --> digits(Digits), { length(Digits, Len), Len =< MaxDigits, Len > 0, number_codes(Value, Digits) }. alnums([H|T]) --> [H], { code_type(H, alnum) }, !, alnums(T). alnums([]) --> "". short_version(version(Major, Minor, Patch, Tag)) --> digits(Digits), { Digits = [D1,D2,D3] -> number_codes(Major, [D1]), number_codes(Minor, [D2]), number_codes(Patch, [D3]) ; Digits = [D1,D2,D3,D4] -> ( number_codes(51, [D1,D2]) % 5.1X.Y -> number_codes(Major, [D1]), number_codes(Minor, [D2,D3]), number_codes(Patch, [D4]) ; number_codes(Major, [D1]), number_codes(Minor, [D2]), number_codes(Patch, [D3,D4]) ) ; Digits = [D1,D2,D3,D4,D5] -> number_codes(Major, [D1]), number_codes(Minor, [D2,D3]), number_codes(Patch, [D4,D5]) }, tag(Tag), !. short_version(latest) --> "latest". %% sort_files(+In, -Out, +Options) % % Sort files by type and version. Type: linux, windows, mac, src, % doc. Versions: latest first. % % Options: % % * show(Show) % One of =all= or =latest=. sort_files(In, Out, Options) :- map_list_to_pairs(map_type, In, Typed0), ( option(show(all), Options) -> Typed = Typed0 ; exclude(old_tagged_file, Typed0, Typed) ), keysort(Typed, TSorted), group_pairs_by_key(TSorted, TGrouped), maplist(sort_group_by_version, TGrouped, TGroupSorted), ( option(show(all), Options) -> pairs_values(TGroupSorted, TValues), flatten(TValues, Out) ; take_latest(TGroupSorted, Out) ). map_type(File, Tag) :- File = file(Type, Platform, _Version, _Name, _Path), type_tag(Type, Platform, Tag). type_tag(bin, linux(A), tag(10, linux(A))) :- !. type_tag(bin, linux(A,B), tag(11, linux(A,B))) :- !. type_tag(bin, windows(A), tag(Tg, windows(A))) :- !, win_tag(A, Tg2), Tg is 20+Tg2. type_tag(bin, macos(A,B), tag(Tg, macos(A,B))) :- !, mac_tag(A, Tg2), Tg is 30+Tg2. type_tag(src, Format, tag(40, Format)) :- !. type_tag(doc, Format, tag(50, Format)) :- !. type_tag(X, Y, tag(60, X-Y)). mac_tag(bundle, 4). mac_tag(snow_leopard_and_later, 5). mac_tag(lion, 6). mac_tag(snow_leopard, 7). mac_tag(leopard, 8). mac_tag(tiger, 9). win_tag(win64, 1). win_tag(win32, 2). sort_group_by_version(Tag-Files, Tag-Sorted) :- map_list_to_pairs(tag_version, Files, TFiles), keysort(TFiles, TRevSorted), pairs_values(TRevSorted, RevSorted), reverse(RevSorted, Sorted). tag_version(File, Tag) :- File = file(_,_,Version,_,_), version_tag(Version, Tag). version_tag(version(Major, Minor, Patch, Tag), version(Major, Minor, Patch, Order)) :- ( pre_version(Tag, Order) -> true ; print_message(error, error(domain_error(pre_release_version, Tag),_)), Order = pre(-100, 0) ). pre_version('', pre(0, 0)) :- !. pre_version(NrA, pre(0, 0)) :- atom_number(NrA, _Nr), !. pre_version(Tag, pre(TagOrder, N)) :- tag(TagPrefix, TagOrder), atom_concat(TagPrefix, NA, Tag), atom_number(NA, N). tag(rc, -1). tag(beta, -2). tag(alpha, -3). take_latest([], []). take_latest([_-[H|_]|T0], [H|T]) :- !, take_latest(T0, T). take_latest([_-[]|T0], T) :- !, % emty set take_latest(T0, T). %% old_tagged_file(+TypeFile) is semidet. old_tagged_file(tag(_,Type)-_File) :- old_file_type(Type). old_file_type(linux(_)). old_file_type(linux(_,_)). old_file_type(macos(_,ppc)). old_file_type(macos(tiger,_)). old_file_type(macos(snow_leopard_and_later,_)). /******************************* * DOWNLOAD * *******************************/ %% download(+Request) is det. % % Actually download a file. Two special requests are supported: % % - By postfixing the file with `.sha256` you get the SHA256 % checksum rather than the file. % - If you replace the version with `latest` you get an HTTP % 303 (See Other) reply pointing at the latest version. download(Request) :- memberchk(path_info(Download), Request), file_name_extension(File, envelope, Download), !, envelope(File). download(Request) :- memberchk(path_info(Download), Request), ( file_name_extension(File, sha256, Download) -> true ; File = Download ), download_file(File, AbsFile), ( File == Download -> http_peer(Request, Remote), broadcast(download(File, Remote)), http_reply_file(AbsFile, [unsafe(true)], Request) ; file_checksum(AbsFile, SHA256), format('Content-type: text/plain~n~n'), format('~w~n', [SHA256]) ). download(Request) :- memberchk(path_info(Download), Request), classify_file(Download, file(Class,Platform,latest,_,_), [show(last)]), file_directory_name(Download, Dir), absolute_file_name(download(Dir), AbsDir, [ access(read), file_type(directory), file_errors(fail) ]), download_files(AbsDir, Class, Files, [show(last)]), memberchk(file(Class, Platform, _, File, _), Files), !, directory_file_path(Dir, File, Redirect), http_link_to_id(download, path_postfix(Redirect), URI), http_redirect(see_other, URI, Request). download(Request) :- ( memberchk(path_info(Download), Request) -> true ; Download = '.' ), absolute_file_name(download(Download), AbsFile, [ access(read), file_errors(fail), file_type(directory) ]), !, http_reply_dirindex(AbsFile, [ unsafe(true), name(name_cell) ], Request). download(Request) :- memberchk(path(Path), Request), existence_error(http_location, Path). download_file(File, AbsFile) :- absolute_file_name(download(File), AbsFile, [ access(read), file_errors(fail) ]). :- public name_cell//1. name_cell(File) --> { needs_envelope(File), file_base_name(File, Name), uri_encoded(path, Name, Ref0), file_name_extension(Ref0, envelope, Ref) }, html(a(href(Ref), Name)). name_cell(File) --> { file_base_name(File, Name), uri_encoded(path, Name, Ref) }, html(a(href(Ref), Name)). %% download_daily(+Request) % % Provide the download page for the windows binaries. download_daily(_Request) :- absolute_file_name(download('daily/bin'), Dir, [ file_type(directory), access(read) ]), reply_html_page( download(Dir, 'Download daily builds for Windows'), title('Download daily builds for Windows'), [ \explain_win_daily, \directory_index(Dir, [ order_by(time), order(descending), name(name_cell) ]) ]). explain_win_daily --> html({|html||

The table below provides access to the most recent 7 daily builds of SWI-Prolog for Windows, both the 32- and 64-bit versions. The build is done automatically from the GIT sources. The files use the following naming convention:

Please note that these versions may be unstable! It is adviced to follow current discussions on the mailing list and/or the git commit messages at GitHub. The primary purpose of the daily builds is to quickly provide binaries after a bug report.

|}). /******************************* * ENVELOPE * *******************************/ needs_envelope(File) :- file_name_extension(_, exe, File). add_envelope(File, Envelope) :- needs_envelope(File), !, file_name_extension(File, envelope, Envelope). add_envelope(File, File). envelope(File) :- maybe(0.1), download_file(File, AbsFile), file_checksum(AbsFile, OkHash), compute_file_checksum(AbsFile, NewHash), NewHash \== OkHash, !, reply_html_page( download(File, 'Possibly tampered binary'), title('Possibly tampered binary'), \tampered(File, OkHash, NewHash)). envelope(File) :- file_base_name(File, Base), reply_html_page( download(Base, 'Download binary'), title('Download a binary file'), \envelope(File)). envelope(File) --> { http_absolute_location(icons('alert.gif'), Alert, []), http_absolute_location(icons('vt_logo.png'), VTLogo, []), download_file(File, AbsFile), file_checksum(AbsFile, Hash), file_base_name(File, Base), format(atom(VTHREF), 'https://www.virustotal.com/file/~w/analysis/', Hash) }, html({|html(Base, Hash, VTHREF, VTLogo, Alert)||

Windows antivirus software works using signatures and heuristics. Using the huge amount of virusses and malware known today, arbitrary executables are often falsily classified as malicious. Google Safe Browsing, used by most modern browsers, therefore often classifies our Windows binaries as malware. You can use e.g., virustotal to verify files with a large number of antivirus programs.

Our Windows binaries are cross-compiled on an isolated Linux container. The integrity of the binaries on the server is regularly verified by validating its SHA256 fingerprint.

Please select the checkbox below to enable the actual download link.

I understand
Download Base (SHA256: Hash)
VIRUSTOTAL Scan Result
|}), js_script({|javascript(Base)|| $(function() { $("#understand").prop("checked", false) .on("click", function() { $("#download").attr("href", Base); }); }); |}). tampered(File, OkHash, NewHash) --> { http_absolute_location(icons('alert.gif'), Alert, []) }, html({|html(File, Alert, OkHash, NewHash)||

The file File SHA256 signature has changed. Please report this at bugs@swi-prolog.org |}). /******************************* * CHECKSUMS * *******************************/ :- persistent sha256(path:atom, sha256:atom). attach_db :- db_attached('checksum.db'), !. attach_db :- db_attach('checksum.db', []). %! file_checksum(+Path:atom, -Sum:atom) is det. % % True when Sum is the SHA256 checksum of file. We keep this in % the Prolog database because this simplifies uploading files. % Although the data under control of the server and thus more % vulnerable than the download area on disk because that is not % writeable by the server, I think this is also better from a % security point of view because it requires the attacker to both % modify the filesystem and the server, something that requires % different rights and expertise. file_checksum(Path, Sum) :- attach_db, sha256(Path, Sum0), !, Sum = Sum0. file_checksum(Path, Sum) :- compute_file_checksum(Path, Sum). compute_file_checksum(Path, Sum) :- crypto_file_hash(Path, Sum, [ encoding(octet), algorithm(sha256) ]), assert_sha256(Path, Sum).