NAME
    Setup::File - Setup file (existence, mode, permission, content)

VERSION
    version 0.09

SYNOPSIS
     use Setup::File 'setup_file';

     # simple usage (doesn't save undo data)
     my $res = setup_file path => '/etc/rc.local',
                          should_exist => 1,
                          gen_content_code => sub { "#!/bin/sh\n" },
                          owner => 'root', group => 0,
                          mode => '+x';
     die unless $res->[0] == 200 || $res->[0] == 304;

     # perform setup and save undo data (undo data should be serializable)
     $res = setup_file ..., -undo_action => 'do';
     die unless $res->[0] == 200 || $res->[0] == 304;
     my $undo_data = $res->[3]{undo_data};

     # perform undo
     $res = setup_file ..., -undo_action => "undo", -undo_data=>$undo_data;
     die unless $res->[0] == 200 || $res->[0] == 304;

DESCRIPTION
    This module provides one function: setup_file.

    This module is part of the Setup modules family.

    This module uses Log::Any logging framework.

    This module's functions have Sub::Spec specs.

THE SETUP MODULES FAMILY
    I use the "Setup::" namespace for the Setup modules family. These family
    of modules are typically used in installers. They must support
    uninstallation (reverse/undo) and be flexible enough to deal with
    external state changes.

    To "setup" something means to set something into a desired state. For
    example, Setup::File sets up a file with a specified permission mode,
    ownership, and content. If the file doesn't exist it will be created; if
    a directory exists instead, it will be removed and replaced with the
    file; if the file already exists but with incorrect
    permission/owner/content, it will be corrected. If everything is already
    correct, nothing is done (the function returns 304 status). Another
    example is Setup::Unix::User, which will setup a Unix user (with the
    correct specified group membership).

    A simulation (dry run) mode also exists: if you pass -dry_run => 1
    argument to the function, it will check states and report
    inconsistencies, but will modify nothing and return 200 status (or 304)
    immediately instead. See the "dry_run" feature in
    Sub::Spec::Clause::features for more details on dry running.

    After the setup, the function returns undo data, which can be used to
    perform undo ("unsetup", "uninstallation") later. The undo data is
    serializable and thus can be stored in persistent storage. When doing
    undo, the undo data is fed into the "-undo_data" argument, along with
    other same arguments specified during the previous "do" phase. See the
    "undo" feature in Sub::Spec::Clause::features for more details on the
    undo protocol. Undo will reverse all actions done by the function in the
    "do" phase; for example, if a file was created by the function it will
    be deleted (if it hasn't changed since the creation), if an existing
    file's mode/ownership was changed, it will be restored, and so on.

    There could be various state changes between the time of do and undo; a
    file can be deleted or modified by other processes. The undo must be
    flexible enough so it can reverse whatever state changes the previous do
    phase did whenever it can, but not disrupt other processes' changes.

    After an undo, the function returns undo_data, which can be used to
    perform undo of undo (redo) later.

  Implementation
    Below is the general view on implementation of a setup module. For more
    details, delve directly into the source code.

    We divide setup into a series of unit steps. For example, setting up a
    file is comprised of steps: create, chown, chmod. Or it can just be:
    set_content, chown, chmod, if the file already exists. Or: rm, create,
    chown, chmod, if a directory exists and must be removed first.

    To perform setup, we begin with an empty list of steps and add necessary
    steps according to the current state. To perform undo, we are given undo
    data, which is just the list of steps generated by previous invocation.

    After we have the list of steps, we perform them one by one
    sequentially. Each step comes with its own state checking and can be
    skipped if the desired state is already reached. After performing a
    step, we also add an undo step to the undo steps list. If an error is
    encountered in a step, we can perform a rollback, which basically means
    we perform the undo steps formed up to that point. (If error is
    encountered during rollback, we die.)

    After all steps have been done successfully, we return 200.

  Undo data
    Undo data should be a list of steps:

     [undo_step1, undo_step2, ...]

    Undo step is usually a command followed by a list of args, examples:

     ["reset"]
     ["rm", "file1"]
     ["do", "Setup::File::setup_file", {arg1=>..., arg2=>...}]
     ["undo", "Setup::File::setup_file", $args, $undo_data]

    Because undo data might be needed much later after it is generated (e.g.
    months or even years later when a software is finally uninstalled),
    please plan a stable list of commands and its arguments carefully, so
    much newer version of your setup module can still perform undo using
    undo data produced by older version of your setup module. Existing
    commands should still be supported as long as possible, unless
    absolutely necessary that it is abandoned. Changes in the order of
    command arguments should also be kept minimal.

FUNCTIONS
    None are exported by default, but they are exportable.

  setup_file(%args) -> [STATUS_CODE, ERR_MSG, RESULT]
    Setup file (existence, mode, permission, content).

    On do, will create file (if it doesn't already exist) and correct
    mode/permission as well as content.

    On undo, will restore old mode/permission/content, or delete the file
    again if it was created by this function *and* its content hasn't
    changed since.

    If given, -undo_hint should contain {tmp_dir=>...} to specify temporary
    directory to save replaced file/dir. Temporary directory defaults to
    ~/.setup, it will be created if not exists.

    Returns a 3-element arrayref. STATUS_CODE is 200 on success, or an error
    code between 3xx-5xx (just like in HTTP). ERR_MSG is a string containing
    error message, RESULT is the actual result.

    This function supports undo operation. See Sub::Spec::Clause::features
    for details on how to perform do/undo/redo.

    This function supports dry-run (simulation) mode. To run in dry-run
    mode, add argument "-dry_run" => 1.

    Arguments ("*" denotes required arguments):

    *   path* => *str*

        Path to file.

        File path needs to be absolute so it's normalized.

    *   allow_symlink* => *bool* (default 0)

        Whether symlink is allowed.

        If existing file is a symlink then if allow_symlink is false then it
        is an unacceptable condition (the symlink will be replaced if
        replace_symlink is true).

        Note: if you want to setup symlink instead, use Setup::Symlink.

    *   check_content_code => *code*

        Code to check content.

        If unset, file will not be checked for its content. If set, code
        will be called whenever file content needs to be checked. Code will
        be passed the file content and should return a boolean value
        indicating whether content is acceptable.

        Alternatively you can use the simpler 'content' argument.

    *   content => *str*

        Desired file content.

        Alternatively you can also use check_content_code &
        gen_content_code.

    *   gen_content_code => *code*

        Code to generate content.

        If set, whenever a new file content is needed (e.g. when file is
        created or file content reset), this code will be called to provide
        it. If unset, empty string will be used instead.

        Code will be passed the current content (or undef) and should return
        the new content.

        Alternatively you can use the simpler 'content' argument.

    *   group => *str*

        Expected group.

    *   mode => *str*

        Expected permission mode.

    *   owner => *str*

        Expected owner.

    *   replace_dir* => *bool* (default 1)

        Replace existing dir if it needs to be replaced.

    *   replace_file* => *bool* (default 1)

        Replace existing file if it needs to be replaced.

    *   replace_symlink* => *bool* (default 1)

        Replace existing symlink if it needs to be replaced.

    *   should_exist => *bool*

        Whether file should exist.

        If undef, file need not exist. If set to 0, file must not exist and
        will be deleted if it does. If set to 1, file must exist and will be
        created if it doesn't.

SEE ALSO
    Other modules in Setup:: namespace.

AUTHOR
    Steven Haryanto <stevenharyanto@gmail.com>

COPYRIGHT AND LICENSE
    This software is copyright (c) 2011 by Steven Haryanto.

    This is free software; you can redistribute it and/or modify it under
    the same terms as the Perl 5 programming language system itself.

