TeXLive::TLUtils - TeX Live infrastructure miscellany


use TeXLive::TLUtils;

Platform detection


System tools

TeXLive::TLUtils::run_cmd($cmd [, @envvars ]);
TeXLive::TLUtils::system_pipe($prog, $infile, $outfile, $removeIn, @args);

File utilities

TeXLive::TLUtils::rmtree($root, $verbose, $safe);
TeXLive::TLUtils::copy($file, $target_dir);
TeXLive::TLUtils::download_file($path, $destination);
TeXLive::TLUtils::setup_programs($bindir, $platform);
TeXLive::TLUtils::tlcmp($file, $file);

Installer functions

TeXLive::TLUtils::time_estimate($totalsize, $donesize, $starttime)
TeXLive::TLUtils::install_packages($from_tlpdb,$media,$to_tlpdb,$what,$opt_src, $opt_doc, $retry, $continue);
TeXLive::TLUtils::do_postaction($how, $tlpobj, $do_fileassocs, $do_menu, $do_desktop, $do_script);
TeXLive::TLUtils::announce_execute_actions($how, @executes, $what);
TeXLive::TLUtils::add_symlinks($root, $arch, $sys_bin, $sys_man, $sys_info);
TeXLive::TLUtils::remove_symlinks($root, $arch, $sys_bin, $sys_man, $sys_info);
TeXLive::TLUtils::w32_add_to_path($bindir, $multiuser);
TeXLive::TLUtils::w32_remove_from_path($bindir, $multiuser);

Logging and debugging

TeXLive::TLUtils::info($str1, ...);    # output unless -q
TeXLive::TLUtils::debug($str1, ...);   # output if -v
TeXLive::TLUtils::ddebug($str1, ...);  # output if -vv
TeXLive::TLUtils::dddebug($str1, ...); # output if -vvv
TeXLive::TLUtils::log($str1, ...);     # only to log file
TeXLive::TLUtils::tlwarn($str1, ...);  # warn on stderr and log
TeXLive::TLUtils::tldie($str1, ...);   # tlwarn and die
TeXLive::TLUtils::debug_hash_str($label, HASH); # stringified HASH
TeXLive::TLUtils::debug_hash($label, HASH);   # warn stringified HASH
TeXLive::TLUtils::backtrace();                # return call stack as string
TeXLive::TLUtils::process_logging_options($texdir); # handle -q -v* -logfile


TeXLive::TLUtils::push_uniq(\@list, @items);
TeXLive::TLUtils::member($item, @list);
TeXLive::TLUtils::merge_into(\%to, \%from);
TeXLive::TLUtils::compare_tlpobjs($tlpA, $tlpB);
TeXLive::TLUtils::compare_tlpdbs($tlpdbA, $tlpdbB);




Platform detection


If $^O =~ /MSWin/i is true we know that we're on Windows and we set the global variable $::_platform_ to win32. Otherwise we call platform_name with the output of config.guess as argument.

The result is stored in a global variable $::_platform_, and subsequent calls just return that value.

As of 2021, config.guess unfortunately requires a shell that understands the $(...) construct. This means that on old-enough systems, such as Solaris, we have to look for a shell. We use the value of the CONFIG_SHELL environment variable if it is set, else /bin/ksh if it exists, else /bin/bash if it exists, else give up.


Convert the $canonical_host argument, a system description as returned by config.guess, into a TeX Live platform name, that is, a name used as a subdirectory of our bin/ dir. Our names have the form CPU-OS, for example, x86_64-linux.

We need this because what's returned from config.,guess does not match our historical names, e.g., config.guess returns linux-gnu but we need linux.

The CPU part of our name is always taken from the argument, with various transformation.

For the OS part, if the environment variable TEXLIVE_OS_NAME is set, it is used as-is. Otherwise we do our best to figure it out.

This function still handles old systems which are no longer supported, just in case.


Return a string which describes a particular platform identifier, e.g., given i386-linux we return Intel x86 with GNU/Linux.


Return 1 if platform is Windows and 0 otherwise. The test is currently based on the value of Perl's $^O variable.


Return 1 if platform is UNIX and 0 otherwise.

System Tools


Get an environment variable. It is assumed that the environment variable contains a path. On Windows all backslashes are replaced by forward slashes as required by Perl. If this behavior is not desired, use $ENV{"$variable"} instead. 0 is returned if the environment variable is not set.


which does the same as the UNIX command which(1), but it is supposed to work on Windows too. On Windows we have to try all the extensions given in the PATHEXT environment variable. We also try without appending an extension because if $string comes from an environment variable, an extension might already be present.


Initializes a directory for all temporary files. This uses File::Temp and thus honors various env variables like TMPDIR, TMP, and TEMP.


Create a temporary directory which is removed when the program is terminated.


Create a temporary file which is removed when the program is terminated. Returns file handle and file name. Arguments are passed on to File::Temp::tempfile.


chdir($dir) or die.

wsystem($msg, @args)

Call info about what is being done starting with $msg, then run system(@args); tlwarn if unsuccessful and return the exit status.


Call ddebug about what is being done, then run system(@args), and die if unsuccessful.

run_cmd($cmd, @envvars)

Run shell command $cmd and captures its output. Returns a list with CMD's output as the first element and the return value (exit code) as second.

If given, @envvars is a list of environment variable name / value pairs set in %ENV for the call and reset to their original value (or unset if not defined initially).

system_pipe($prog, $infile, $outfile, $removeIn, @extraargs)

Runs $prog with @extraargs redirecting stdin from $infile, stdout to $outfile. Removes $infile if $removeIn is true.


If a POSIX compliant df program is found, returns the number of Mb free at $path, otherwise -1. If $path does not exist, check upwards for two levels for an existing parent, and if found, use it for computing the disk space.


Returns the current user's home directory ($HOME on Unix, $USERPROFILE on Windows, and ~ if none of the two are set. Save in package variable $user_home_dir after computing.


Expands initial ~ with the user's home directory in $str if available, else leave ~ in place.

File utilities


Return both dirname and basename. Example:

($dirpart,$filepart) = dirname_and_basename ($path);

Return $path with its trailing /component removed.


Return $path with any leading directory components removed.


# Other than Cwd::abs_path, tl_abs_path also works if the argument does not # yet exist as long as the path does not contain '..' components.


Tests whether its argument is a directory where we can create a directory.


Tests whether its argument is writable by trying to write to it. This function is necessary because the built-in -w test just looks at mode and uid/gid, which on Windows always returns true and even on Unix is not always good enough for directories mounted from a fileserver.

mkdirhier($path, [$mode])

The function mkdirhier does the same as the UNIX command mkdir -p. It behaves differently depending on the context in which it is called: If called in void context it will die on failure. If called in scalar context, it will return 1/0 on sucess/failure. If called in list context, it returns 1/0 as first element and an error message as second, if an error occurred (and no second element in case of success). The optional parameter sets the permission bits.

rmtree($root, $verbose, $safe)

The rmtree function provides a convenient way to delete a subtree from the directory structure, much like the Unix command rm -r. rmtree takes three arguments:

It returns the number of files successfully deleted. Symlinks are simply deleted and not followed.

NOTE: There are race conditions internal to the implementation of rmtree making it unsafe to use on directory trees which may be altered or moved while rmtree is running, and in particular on any directory trees with any path components or subdirectories potentially writable by untrusted users.

Additionally, if the third parameter is not TRUE and rmtree is interrupted, it may leave files and directories with permissions altered to allow deletion (and older versions of this module would even set files and directories to world-read/writable!)

Note also that the occurrence of errors in rmtree can be determined only by trapping diagnostic messages using $SIG{__WARN__}; it is not apparent from the return value.

copy($file, $target_dir)
copy("-f", $file, $destfile)
copy("-L", $file, $destfile)

Copy file $file to directory $target_dir, or to the $destfile if the first argument is "-f". No external programs are involved. Since we need sysopen(), the Perl module is required. The time stamps are preserved and symlinks are created on Unix systems. On Windows, (-l $file) will never return 'true' and so symlinks will be (uselessly) copied as regular files.

If the first argument is "-L" and $file is a symlink, the link is dereferenced before the copying is done. (If both "-f" and "-L" are desired, they must be given in that order, although the codebase currently has no need to do this.)

copy invokes mkdirhier if target directories do not exist. Files start with mode 0777 if they are executable and 0666 otherwise, with the set bits in umask cleared in each case.

$file can begin with a file:/ prefix.

If $file is not readable, we return without copying anything. (This can happen when the database and files are not in perfect sync.) On the other file, if the destination is not writable, or the writing fails, that is a fatal error.


Update modification and access time of @files. Non-existent files are created.


Return a (more or less) minimal list of directories and files, given an original list of files @files. That is, if every file within a given directory is included in @files, replace all of those files with the absolute directory name in the return list. Any files which have sibling files not included are retained and made absolute.

We try to walk up the tree so that the highest-level directory containing only directories or files that are in @files is returned. (This logic may not be perfect, though.)

This is not just a string function; we check for other directory entries existing on disk within the directories of @files. Therefore, if the entries are relative pathnames, the current directory must be set by the caller so that file tests work.

As mentioned above, the returned list is absolute paths to directories and files.

For example, suppose the input list is


If there are no other entries under dir1/, the result will be /absolute/path/to/dir1.


Returns all the directories from which all content will be removed.

Here is the idea:

create a hashes by_dir listing all files that should be removed by directory, i.e., key = dir, value is list of files
for each of the dirs (keys of by_dir and ordered deepest first) check that all actually contained files are removed and all the contained dirs are in the removal list. If this is the case put that directory into the removal list
return this removal list
time_estimate($totalsize, $donesize, $starttime)

Returns the current running time and the estimated total time based on the total size, the already done size, and the start time.

install_packages($from_tlpdb, $media, $to_tlpdb, $what, $opt_src, $opt_doc, $retry, $continue)

Installs the list of packages found in @$what (a ref to a list) into the TLPDB given by $to_tlpdb. Information on files are taken from the TLPDB $from_tlpdb.

$opt_src and $opt_doc specify whether srcfiles and docfiles should be installed (currently implemented only for installation from uncompressed media).

If $retry is trueish, retry failed packages a second time.

If $continue is trueish, installation failure of non-critical packages will be ignored (success is returned).

Returns 1 on success and 0 on error.

do_postaction($how, $tlpobj, $do_fileassocs, $do_menu, $do_desktop, $do_script)

Evaluates the postaction fields in the $tlpobj. The first parameter can be either install or remove. The second gives the TLPOBJ whos postactions should be evaluated, and the last four arguments specify what type of postactions should (or shouldn't) be evaluated.

Returns 1 on success, and 0 on failure.

announce_execute_actions($how, $tlpobj, $what)

Announces that the actions given in $tlpobj should be executed after all packages have been unpacked. $what provides additional information.

These two functions try to create/remove symlinks for binaries, man pages, and info files as specified by the options $sys_bin, $sys_man, $sys_info.

The functions return 1 on success and 0 on error. On Windows it returns undefined.

w32_add_to_path($bindir, $multiuser) =item w32_remove_from_path($bindir, $multiuser)

These two functions try to add/remove the binary directory $bindir on Windows to the registry PATH variable.

If running as admin user and $multiuser is set, the system path will be adjusted, otherwise the user path.

After calling these functions TeXLive::TLWinGoo::broadcast_env() should be called to make the changes immediately visible.

check_file_and_remove($what, $checksum, $checksize

Remove the file $what if either the given $checksum or $checksize for $what does not agree with our recomputation using TLCrypto::tlchecksum and stat, respectively. If a check argument is not given, that check is not performed. If the checksums agree, the size is not checked. The return status is random.

This unusual behavior (removing the given file) is because this is used for newly-downloaded files; see the calls in the unpack routine (which is the only caller).

unpack($what, $targetdir, @opts

If necessary, downloads C$what>, and then unpacks it into $targetdir. @opts is assigned to a hash and can contain the following keys: tmpdir (use this directory for downloaded files), checksum (check downloaded file against this checksum), size (check downloaded file against this size), remove (remove temporary files after operation).

Returns a pair of values: in case of error return 0 and an additional explanation, in case of success return 1 and the name of the package.

If checksum or size is -1, no warnings about missing checksum/size is printed. This is used during restore and unwinding of failed updates.

untar($tarfile, $targetdir, $remove_tarfile)

Unpacks $tarfile in $targetdir (changing directories to $targetdir and then back to the original directory). If $remove_tarfile is true, unlink $tarfile after unpacking.

Assumes the global $::progs{"tar"} has been set up.

tlcmp($file, $file)

Compare two files considering CR, LF, and CRLF as equivalent. Returns 1 if different, 0 if the same.


Return contents of FILE as a string, converting all of CR, LF, and CRLF to just LF.

setup_programs($bindir, $platform, $tlfirst)

Populate the global $::progs hash containing the paths to the programs lz4, tar, wget, xz. The $bindir argument specifies the path to the location of the xz binaries, the $platform gives the TeX Live platform name, used as the extension on our executables. If a program is not present in the TeX Live tree, we also check along PATH (without the platform extension.)

If the $tlfirst argument or the TEXLIVE_PREFER_OWN envvar is set, prefer TL versions; else prefer system versions (except for Windows tar.exe, where we always use ours).

Check many different downloads and compressors to determine what is working.

Return 0 if failure, nonzero if success.

download_file( $relpath, $destination )

Try to download the file given in $relpath from $TeXLiveURL into $destination, which can be either a filename of simply |. In the latter case a file handle is returned.

Downloading first checks for the environment variable TEXLIVE_DOWNLOADER, which takes various built-in values. If not set, the next check is for TL_DOWNLOAD_PROGRAM and TL_DOWNLOAD_ARGS. The former overrides the above specification devolving to wget, and the latter overrides the default wget arguments.

TL_DOWNLOAD_ARGS must be defined so that the file the output goes to is the first argument after the TL_DOWNLOAD_ARGS. Thus, for wget it would end in -O. Use with care.

nulldev ()

Return /dev/null on Unix and nul on Windows.

get_full_line ($fh)

returns the next line from the file handle $fh, taking continuation lines into account (last character of a line is \, and no quoting is parsed).

Installer Functions


Generate a skeleton of empty directories in the TEXMFSYSVAR tree.


Generate a skeleton of empty directories in the TEXMFLOCAL tree, unless TEXMFLOCAL already exists.

create_fmtutil($tlpdb, $dest)
create_updmap($tlpdb, $dest)
create_language_dat($tlpdb, $dest, $localconf)
create_language_def($tlpdb, $dest, $localconf)
create_language_lua($tlpdb, $dest, $localconf)

These five functions create fmtutil.cnf, updmap.cfg, language.dat, language.def, and language.dat.lua respectively, in $dest (which by default is below $TEXMFSYSVAR). These functions merge the information present in the TLPDB $tlpdb (formats, maps, hyphenations) with local configuration additions: $localconf.

Currently the merging is done by omitting disabled entries specified in the local file, and then appending the content of the local configuration files at the end of the file. We should also check for duplicates, maybe even error checking.


Logging and debugging messages.


Internal routine to write message to both $out (references to filehandle) and $::LOGFILE, at level $level, of concatenated items in @rest. If the log file is not initialized yet, the message is saved to be logged later (unless the log file never comes into existence).

info ($str1, $str2, ...)

Write a normal informational message, the concatenation of the argument strings. The message will be written unless -q was specified. If the global $::machinereadable is set (the --machine-readable option to tlmgr), then output is written to stderr, else to stdout. If the log file (see process_logging_options) is defined, it also writes there.

It is best to use this sparingly, mainly to give feedback during lengthy operations and for final results.

debug ($str1, $str2, ...)

Write a debugging message, the concatenation of the argument strings. The message will be omitted unless -v was specified. If the log file (see process_logging_options) is defined, it also writes there.

This first level debugging message reports on the overall flow of work, but does not include repeated messages about processing of each package.

ddebug ($str1, $str2, ...)

Write a deep debugging message, the concatenation of the argument strings. The message will be omitted unless -v -v (or higher) was specified. If the log file (see process_logging_options) is defined, it also writes there.

This second level debugging message reports messages about processing each package, in addition to the first level.

dddebug ($str1, $str2, ...)

Write the deepest debugging message, the concatenation of the argument strings. The message will be omitted unless -v -v -v was specified. If the log file (see process_logging_options) is defined, it also writes there.

In addition to the first and second levels, this third level debugging message reports messages about processing each line of any tlpdb files read, and messages about files tested or matched against tlpsrc patterns. This output is extremely voluminous, so unless you're debugging those parts of the code, it just gets in the way.

log ($str1, $str2, ...)

Write a message to the log file (and nowhere else), the concatenation of the argument strings. The log file may not ever be defined (e.g., the -logfile option isn't given), in which case the message will never be written anywhere.

tlwarn ($str1, $str2, ...)

Write a warning message, the concatenation of the argument strings. This always and unconditionally writes the message to standard error; if the log file (see process_logging_options) is defined, it also writes there.

tldie ($str1, $str2, ...)

Uses tlwarn to issue a warning for @_ preceded by a newline, then exits with exit code 1.

debug_hash_str($label, HASH)

Return LABEL followed by HASH elements, followed by a newline, as a single string. If HASH is a reference, it is followed (but no recursive derefencing).

debug_hash($label, HASH)

Write the result of debug_hash_str to stderr.


Return call(er) stack, as a string.

process_logging_options ($texdir)

This function handles the common logging options for TeX Live scripts. It should be called before GetOptions for any program-specific option handling. For our conventional calling sequence, see (for example) the tlpfiles script.

These are the options handled here:


Omit normal informational messages.


Include debugging messages. With one -v, reports overall flow; with -v -v (or -vv), also reports per-package processing; with -v -v -v (or -vvv), also reports each line read from any tlpdb files. Further repeats of -v, as in -v -v -v -v, are accepted but ignored. -vvvv is an error.

The idea behind these levels is to be able to specify -v to get an overall idea of what is going on, but avoid terribly voluminous output when processing many packages, as we often are. When debugging a specific problem with a specific package, -vv can help. When debugging problems with parsing tlpdb files, -vvv gives that too.

-logfile file

Write all messages (informational, debugging, warnings) to file, in addition to standard output or standard error. In TeX Live, only the installer sets a log file by default; none of the other standard TeX Live scripts use this feature, but you can specify it explicitly.

See also the info, debug, ddebug, and tlwarn functions, which actually write the messages.


A few ideas from Fabrice Popineau's


The sort_uniq function sorts the given array and throws away multiple occurrences of elements. It returns a sorted and unified array.

push_uniq(\@list, @new_items)

The push_uniq function pushes each element in the last argument @ITEMS to the $LIST referenced by the first argument, if it is not already in the list.

member($item, @list)

The member function returns true if the first argument is also inclued in the list of the remaining arguments.

merge_into(\%to, \%from)

Merges the keys of %from into %to.


Test whether installation with TEXDIR set to $texdir should be ok, e.g., would be a creatable directory. Return 1 if ok, 0 if not.

Writable or not, we will not allow installation to the root directory (Unix) or the root of a drive (Windows).

We also do not allow paths containing various special characters, and print a message about this if second argument WARN is true. (We only want to do this for the regular text installer, since spewing output in a GUI program wouldn't be good; the generic message will have to do for them.)

This function takes a single argument path and returns it with " chars surrounding it on Unix. On Windows, the " chars are only added if path contains special characters, since unconditional quoting leads to errors there. In all cases, any " chars in path itself are (erroneously) eradicated.

This function returns a "Windows-ized" version of its single argument path, i.e., replaces all forward slashes with backslashes, and adds an additional " at the beginning and end if path contains any spaces. It also makes the path absolute. So if $path does not start with one (arbitrary) characer followed by :, we add the output of `cd`.

The result is suitable for running in shell commands, but not file tests or other manipulations, since in such internal Perl contexts, the quotes would be considered part of the filename.

The next two functions are meant for user input/output in installer menus. They help making the windows user happy by turning slashes into backslashes before displaying a path, and our code happy by turning backslashes into forwars slashes after reading a path. They both are no-ops on Unix.


Set up to use persistent connections using LWP/TLDownload, that is look for a download server. Return the TLDownload object if successful, else false.


Return a particular mirror given by the generic CTAN auto-redirecting default (specified in $TLConfig::TexLiveServerURL) if we get a response, else the empty string.

Use curl if it is listed as a working_downloader, else wget, else give up. We can't support arbitrary downloaders here, as we do for regular package downloads, since certain options have to be set and the output has to be parsed.

We try invoking the program three times (hardwired).


Check if MIRROR is functional.

1. get a mirror (retries 3 times to contact
   - if no mirror found, use one of the backbone servers
   - if it is an http server return it (no test is done)
   - if it is a ftp server, continue
2. if the ftp mirror is good, return it
3. if the ftp mirror is bad, search for http mirror (5 times)
4. if http mirror is found, return it (again, no test,)
5. if no http mirror is found, return one of the backbone servers

create_mirror_list returns the lists of viable mirrors according to, in a list which also contains continents, and country headers.

extract_mirror_entry extracts the actual repository data from one of these entries.



Reads the whole file and returns the content in a scalar.


If $url is a url, tries to download the file into a temporary file. Otherwise assume that $url is a local file. In both cases returns the local file.

Returns the local file name if succeeded, otherwise undef.

compare_tlpobjs($tlpA, $tlpB)

Compare the two passed TLPOBJ objects. Returns a hash:

$ret{'revision'}  = "revA:revB" # if revisions differ
$ret{'removed'}   = \[ list of files removed from A to B ]
$ret{'added'}     = \[ list of files added from A to B ]
$ret{'fmttriggers'} = 1 if the fmttriggers have changed
compare_tlpdbs($tlpdbA, $tlpdbB, @more_ignored_pkgs)

Compare the two passed TLPDB objects, ignoring the packages 00texlive.installer, 00texlive.image, and any passed @more_ignore_pkgs. Returns a hash:

$ret{'removed_packages'} = \[ list of removed packages from A to B ]
$ret{'added_packages'}   = \[ list of added packages from A to B ]
$ret{'different_packages'}->{$package} = output of compare_tlpobjs
mktexupd ()

Append entries to ls-R files. Usage example:

 my $updLSR=&mktexupd();

The first line creates a new object. Only one such object should be created in a program in order to avoid duplicate entries in ls-R files.

add pushes a filename or a list of filenames to a hash encapsulated in a closure. Filenames must be specified with the full (absolute) path. Duplicate entries are ignored.

exec checks for each component of $TEXMFDBS whether there are files in the hash which have to be appended to the corresponding ls-R files and eventually updates the corresponding ls-R files. Files which are in directories not stated in $TEXMFDBS are silently ignored.

If the flag mustexist is set, exec aborts with an error message if a file supposed to be appended to an ls-R file doesn't exist physically on the file system. This option was added for compatibility with the mktexupd shell script. This option shouldn't be enabled in scripts, except for testing, because it degrades performance on non-cached file systems.

setup_sys_user_mode($prg, $optsref, $tmfc, $tmfsc, $tmfv, $tmfsv)

Return two-element list ($texmfconfig,$texmfvar) specifying which directories to use, either user or sys. If $optsref-{'sys'}> is true, we are in sys mode; else if $optsref-{'user'}> is set, we are in user mode; else a fatal error.

If $prg eq "mktexfmt", and $TEXMFSYSVAR/web2c is writable, use it instead of $TEXMFVAR, even if we are in user mode. $TEXMFCONFIG is not switched, however.


Prepend the location of the TeX Live binaries to the PATH environment variable. This is used by (e.g.) fmtutil. The location is found by calling Cwd::abs_path on which('kpsewhich'). We use kpsewhich because it is known to be a true binary executable; $0 could be a symlink into (say) texmf-dist/scripts/, which is not a useful directory for PATH.


Return hash of tags to urls for space-separated list of repositories passed in $r. If passed undef or empty string, die.



Returns the JSON representation of the object $ref is pointing at. This tries to load the JSON Perl module, and uses it if available, otherwise falls back to module internal conversion.

The used backend can be selected by setting the environment variable TL_JSONMODE to either json or texlive (all other values are ignored). If json is requested and the JSON module cannot be loaded the program terminates.


These two crazy functions must be used to get proper JSON true and false in the output independent of the backend used.


The other modules in Master/tlpkg/TeXLive/ (TeXLive::TLConfig and the rest), and the scripts in Master/tlpg/bin/ (especially tl-update-tlpdb), the documentation in Master/tlpkg/doc/, etc.


This script and its documentation were written for the TeX Live distribution ( and both are licensed under the GNU General Public License Version 2 or later.