Search code examples
ftpfile-permissionsshared-hostingbatch-processinglftp

lftp: how to recursively set permissions; firstly by directory than by file


When securing a Drupal or WordPress installation on a shared host that does not expose SSH access (a lousy situation, fwiw) lftp seems like the right approach to batch setting permissions for directories and files. The find command boasts that you can redirect its output, so one should be able to run a find, grep exclude to only match lines ending in "/" meaning a directory, and then set the permissions on such matches to 755 and perform the inverse on file matches and set to 644 and then fine tune specific files, such as settings.php and so forth.

lftp prompt> find . | grep "/$" | xargs chmod -v 755 

Isn't working and I'm sure I have failed to chain these commands in the correct sequence and format.

How to get this to work?

Update: by "isn't working" I mean that the above command produces no output to the console, nor to the lftp error log. It isn't running these commands locally, fwiw. I'll reduce the command as a demonstration:

find . | grep "/$"

Will take the output of "find" and return matches, here, directories, by nature of the string match:

./daily/
./ffmpeg-installer/
./hourly/
./includes/
./includes/database/
./includes/database/mysql/
./and_so_forth_on_down

Which is cool, since I wish to perform a chmod (and internal command for lftp, with support varying by ftp server) So I expand the command like this:

find . | grep "/$" | xargs echo

Which outputs — nothing. No error output, either. The pipe from grep to xargs isn't happening.

My goal is to form the equivalent of:

chmod 755 ./daily/
chmod 755 ./ffmpeg-installer/

In lftp, the chmod command is performing an ftp-server-permissions change, not a local perms change.


Solution

  • For an explanation of why this does not work as expected, read on - for a solution to the given problem, scroll down.

    The answer can be found in the manpage for lftp, which states that

    "[s]ome commands allow redirecting their output (cat, ls, ...) to file or via pipe to external command."

    So, when you are using a pipe like this on a command that does support redirection in lftp, you are piping its output to your local tools, which will eventually result in chmod trying to change the permissions for a file/directory on our local machine, and most likely fail in case you don't coincidally have the same directory layout in your current directory locally - which is probably the problem you encountered.

    The grep + xargs pipe does work, I just tested the following:

    lftp> find -d 2 | grep "/$"
    ./
    ./applications/
    ./lost+found/
    ./netinfo/
    ./packages/
    ./security/
    ./systems/
    lftp> find -d 2 | grep "/$" | xargs echo
    ./ ./applications/ ./lost+found/ ./netinfo/ ./packages/ ./security/ ./systems/
    

    My wild guess is that it did not appear to work for you because you did not specify a max-depth to find and the network connection + buffering in the pipe got in the way. When I try the same on a directory containing many files/subfolders it takes really long to finish and print. Did the command actually finish for you without output?

    But still, what you are trying to do is not possible. As I stated, the right-hand-side of the pipe works with external commands (even if an inbuilt of the same name exists) as explained by the manual, so

    lftp> chmod 644 foobar
    

    and

    lftp> echo "foobar" | xargs chmod 644
    

    are not equivalent. Yes, chmod is an inbuilt but used in a pipe in the client it will not execute the inbuilt - the manpage clearly states this and you can easily test this yourself. Try the following commands and check their output:

    lftp> echo foo | uname -a
    lftp> echo foo | ls -al
    lftp> echo foo | chmod --help
    lftp> chmod --help
    

    Solution

    As far as a solution to your problem is concerned, you can try something along the lines of:

    #!/bin/bash
    
    server="ftp.foo.bar"
    root_folder="/my/path"
    
    {
            {
                    lftp "${server}" <<EOF
                    cd "${root_folder}"
                    find | grep "/$"
                    quit
    EOF
            } | awk '{ printf "chmod 755 \"%s\"\n", $0 }'
    
            {
                    lftp "${server}" <<EOF
                    cd "${root_folder}"
                    find | grep -v "/$"
                    quit
    EOF
            } | awk '{ printf "chmod 644 \"%s\"\n", $0 }'
    } | lftp "${server}"
    

    This logs in to your server, cds to the folder where you want to recursively start changing the permissions, uses find + grep to find all directories, logs out, pipes this file list into awk to build chmod commands around it, repeats the whole process for files and then pipes the whole list of commands into a new lftp invocation to actually run the generated chmod commands.

    You will also have to add your credentials to the lftp invocations and you might want to comment out the final | lftp "${server}" to check if it produces the desired output before you actually run the whole thing. Please report back if this works for you!