14

As we know "Everything in Linux" is a file, and moreover directory is just a file containing other files.

So, I don't know if this "crazy idea" possible, but it should be in someway according to the above principle.

In simple words, how could I change an existing empty file into a directory. Is it possible?

As some brain storm I thought a some modification in file metadata and make it as directory metadata should make it!!

Any information is appreciated.


UPDATE: For sure I don't want to delete a file and create the dir instead! I'm just trying to know how much the philosophy above is applicable if you can play with some file metadata.

  • 1
    What may be a reason for doing that? – Pilot6 May 21 '15 at 16:26
  • 1
    The only correct way is to delete file and create a directory. Otherwise file system may be broken. You can do it at low level but it depends on file system. In ext4 inode should be edited, I think. – Pilot6 May 21 '15 at 16:32
  • 2
    The "file" concept is not about that. Devices are also treated as files, but that does not mean that you can convert a file to a device. :)) – Pilot6 May 21 '15 at 16:35
  • 5
    debugfs has a modify_inode command that allows you to edit an inode directly which would allow you to set the file flag to a dir. It also has an mkdir <inode> command. I've not done any of thid and I'm not about to try. – tallus May 21 '15 at 17:12
  • 4
    This assumption "As we know "Everything in Linux" is a file," is wrong so your whole question falls apart. As we know "Everything in Linux is a file DESCRIPTOR". Makes a world of a difference. – Rinzwind May 22 '15 at 16:45
21

Achieving the conversion

Creating a test filesystem

In order to preserve our main filesystem from any possible damage after running this experiment, we're going to create a small filesystem inside a normal file for test purposes.

  1. Create a zero-filled file called test with a size of 10 megabytes:

    dd if=/dev/zero of=~/test bs=10M count=1
    
  2. Create an Ext4 filesystem inside the file, as if it were a partition:

    mkfs.ext4 ~/test
    

Creating some files and directories

Now we have a fully functional filesystem inside the test file, so we're going to create some files and directories inside it.

  1. Mount the newly created filesystem inside /mnt:

    sudo mount ~/test /mnt
    
  2. Create a file and a directory:

    sudo mkdir /mnt/folder
    echo "contents" | sudo tee /mnt/file
    
  3. Check the contents of the filesystem:

    ls -l /mnt
    

    Output should be something like this:

    total 2
    -rw-r--r-- 1 root root     0 may 21 18:53 file
    drw-r--r-- 2 root root  1024 may 21 18:55 folder
    
  4. Unmount the test filesystem:

    sudo umount /mnt
    

Swapping the file and the folder

  1. Run debugfs against the test file with write permission (-w flag):

    debugfs -w ~/test
    
  2. Convert file into a folder:

    • At the debugfs prompt, type this:

      modify_inode file
      
    • A prompt will appear asking you a mode; type this:

      040644
      
    • Keep pressing return to leave the remaining data as-is until the prompt appears again.

  3. Convert folder into a file:

    • At the debugfs prompt, type this:

      modify_inode folder
      
    • A prompt will appear asking you a mode; type this:

      0100644
      
    • Keep pressing return to leave the remaining data as-is until the prompt appears again.

  4. To exit debugfs prompt, simply hit q and then return

Checking the success of the operation

  1. Mount the test filesystem again:

    sudo mount ~/test /mnt
    
  2. Check the filesystem contents:

    ls -l /mnt
    

    Now, it should show the file as if it were a directory and vice versa:

    total 2
    drw-r--r-- 1 root root     0 may 21 18:53 file
    -rw-r--r-- 2 root root  1024 may 21 18:55 folder
    

Script to calculate inode modes

#!/bin/bash

#### See https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Table

## Terminal measures:
x="$(( $(tput cols) / 2 ))"   # Width of the terminal
y="$(( $(tput lines) /  2 ))" # Height of the terminal

## File descriptors:
declare -A types       # Declare an associative array with file descriptors
types[f]='0x8000'      # File
types[l]='0xA000'      # Link
types[s]='0xC000'      # Socket
types[d]='0x4000'      # Directory
types[p]='0x1000'      # Named pipe
types[b]='0x6000'      # Block device
types[c]='0x2000'      # Character device

## Permissions:
declare -A permission  # Declare an associative array with permissions
permission[user_S]='0x800'  # UID
permission[user_s]='0x840'  # UID and user can execute
permission[user_r]='0x100'  # User can read
permission[user_w]='0x80'   # User can write
permission[user_x]='0x40'   # User can execute
permission[group_S]='0x400' # GID
permission[group_s]='0x408' # GID and group can execute
permission[group_r]='0x20'  # Group can read
permission[group_w]='0x10'  # Group can write
permission[group_x]='0x8'   # Group can execute
permission[other_T]='0x200' # Sticky bit
permission[other_t]='0x201' # Sticky bit and other can execute
permission[other_r]='0x4'   # Other can read
permission[other_w]='0x2'   # Other can write
permission[other_x]='0x1'   # Other can execute

## Cleanup function:
function cleanup() {
    tput cvvis        # Make the cursor visible
    tput rmcup        # Restore saved terminal contents
    stty sane         # Fix problems caused by read -s
    exit 0            # Exit gracefully
}

## Function to print at a specified position:
function pprint() {
    tput cup $1 $2
    printf "${@:3}"
}

## Function to clear the notification area:
function reset() {
    pprint $((y+2)) $((x-40)) ' %.0s' {1..25} # Print 25 spaces
}

## Function to notify something to the user:
function notify() {
    reset                          # Clear the notification area
    pprint $((y+2)) $((x-40)) "$@" # Print the notification text
}

## If the terminal is smaller than 100x8, exit gracefully (self-explainatory):
if [ $x -lt 50 ] || [ $y -lt 5 ]; then
    echo 'Error, I need a minimum of 100x10 lines to run'
    exit 0
fi

## Initialize the terminal:
trap cleanup EXIT SIGHUP SIGINT SIGTERM # Call cleanup function after receiving ^C
stty -echo  cbreak                      # Put terminal in silent mode
tput smcup                              # Save terminal contents
tput civis                              # Make the cursor inisible

## Draw the big box:
printf '\033[1;37m'                            # Color
pprint $((y-3)) $((x-48)) '\u2500%.0s' {1..97} # Upper line
pprint $((y+4)) $((x-48)) '\u2500%.0s' {1..97} # Lower line
for ((i=4;i>-4;i--)); do                       # Sides:
    pprint $((y+i)) $((x-49)) '\u2502'             # Left line
    pprint $((y+i)) $((x+49)) '\u2502'             # Right line
done                                           # End sides
pprint $((y-3)) $((x-49)) '\u256D'             # Upper-left corner
pprint $((y+4)) $((x-49)) '\u2570'             # Lower-left corner
pprint $((y-3)) $((x+49)) '\u256E'             # Upper-right corner
pprint $((y+4)) $((x+49)) '\u256F'             # Lower-right corner

## Draw the small box:
printf '\033[1;35m'                             # Color
pprint $((y+1)) $((x-10)) '\u2501%.0s' {1..10}  # Upper line
pprint $((y+3)) $((x-10)) '\u2501%.0s' {1..10}  # Lower line
pprint $((y+2)) $((x-11)) '\u2503'              # Left line
pprint $((y+2)) $((x+00)) '\u2503'              # Right line
pprint $((y+1)) $((x-11)) '\u250F'              # Upper-left corner
pprint $((y+3)) $((x-11)) '\u2517'              # Lower-left corner
pprint $((y+1)) $((x+00)) '\u2513'              # Upper-right corner
pprint $((y+3)) $((x+00)) '\u251B'              # Lower-right corner

## Print type help:
pprint $((y-2)) $((x-44)) '\033[0;37mInode type: \033[1;37mf\033[0;37mile, \033[1;37md\033[0;37mirectory, \033[1;37ml\033[0;37mink, named \033[1;37mp\033[0;37mipe, \033[1;37ms\033[0;37mocket, \033[1;37mc\033[0;37mharacter device or \033[1;37mb\033[0;37mlock device.'

## Print permission help:
pprint $((y-1)) $((x-40)) '\033[0;36mPermission (\033[1;32mu\033[0;32mser\033[0;36m, \033[1;33mg\033[0;33mroup\033[0;36m or \033[1;31mo\033[0;31mther\033[0;36m): \033[1;36mr\033[0;36mead, \033[1;36mw\033[0;36mrite, e\033[1;36mx\033[0;36mecute, \033[1;36mhyphen\033[0;36m or \033[1;36mspace\033[0;36m to skip.'
pprint $((y+0)) $((x+8)) 's\033[1;36mt\033[0;36micky bit and executable, '
pprint $((y+1)) $((x+8)) 's\033[1;36mT\033[0;36micky bit not executable, '
pprint $((y+2)) $((x+8)) '\033[1;36ms\033[0;36metuid/setgid and executable, '
pprint $((y+3)) $((x+8)) '\033[1;36mS\033[0;36metuid/setgid not executable. '

## Endless loop:
while :; do

    ## Clear the input area:
    pprint $((y+2)) $((x-10)) '% *s\n' 10         # Print 16 spaces

    ## Print mask in the input area:
    printf '\033[1;37m'                           # Color for the type
    pprint $((y+2)) $((x-10)) '\u2588'            # Block for the type
    printf '\033[1;36m'                           # Color for the permision
    pprint $((y+2)) $((x- 9)) '\u2588%.0s' {1..9} # Blocks for the permission

    ## Loop through all variables to make a proper input:
    for var in type {user,group,other}_{r,w,x}; do

        ## Assign colors and regex to fields:
        case "$var" in
            (type)    color='\033[1;37m';     regex='^[fdlpscb]$'    ;;

            (other_x)                         regex='^[-xtT]$'       ;;&
            (user_x|group_x)                  regex='^[-xsS]$'       ;;&
            (user_[rw]|group_[rw]|other_[rw]) regex="^[-${var: -1}]$";;&

            (user*)   color='\033[1;32m'                             ;;
            (group*)  color='\033[1;33m'                             ;;
            (other*)  color='\033[1;31m'                             ;;
        esac

        ## Change the pointer position:
        pprint $((y+3)) $(((x-10)+pointer)) "${color}\u2501"           # Print the pointer on its new position
        if (( pointer > 0 )); then                                     # If the pointer is not in the first position:
            pprint $((y+3)) $(((x-10)+(pointer-1))) '\033[1;35m\u2501'     # Clear the old pointer         
        fi

        ## Infinite loop until there is a valid input for the current character:
        while :; do
            printf "$color"                       # Set the character color
            IFS= read -rn 1 $var                  # Read a character (even if it's a space)

            declare $var="${!var// /-}"           # Convert spaces to hyphens.
            if [[ "$var" == "type" ]]; then       # If the current variable is type:
                declare $var="${!var//-/f}"           # Convert "-" to "f"
            fi

            if [[ "${!var}"  =~ $regex ]]; then   # If there is a valid input:
                reset                                 # Clear error notification if any
                break                                 # Exit from this loop
            else                                  # Else:
                notify "\033[1;31mWrong input!"       # Print the error message
            fi
        done

        ## Print the entered value:
        pprint $((y+2)) $(((x-10)+pointer)) "${!var}"

        ## Sum the current permission:
        ((mode+=permission[${var%_*}_${!var}]))

        ## Increment the pointer:
        ((pointer++))
    done

    ## Post-read:
    unset pointer                                 # Reset the pointer
    pprint $((y+3)) $((x-1)) "\033[1;35m\u2501"   # Clear the pointer
    read -n 1                                     # Wait for Return or another character

    ## Sum file descriptor type:
    ((mode+=${types[$type]}))

    ## Final commands:
    mode=$(printf "%o" $mode)                      # Convert mode to octal (before this was decimal)
    notify "\033[1;32mOctal mode:\033[1;34m $mode" # Print the octal mode
    unset mode                                     # Reset the mode
done

View script on GitHub

Handicaps

  • The folder doesn't open. You can't open it unless you put on it the "raw folder data" that contained it originally.

Further reading

https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Inode_Table


Thanks to @tallus. He gave me a great hint:

debugfs has a modify_inode command that allows you to edit an inode directly which would allow you to set the file flag to a dir.

| improve this answer | |
  • 2
    +1 Nice answer, but just notes 0100755 must be 0100644 for not changing permissions of a file since 755 will give execution for the converted file... – Maythux May 22 '15 at 5:39
  • Also, The newly converted directory will not open. It shows file become folder but folder can't be opened. Do you have some solution for that? – Maythux May 22 '15 at 5:40
  • I'll keep it open for some while, else if no another great answer I'm gonna mark yours – Maythux May 22 '15 at 5:41
  • 3
    Damn this is nice @helio :D – Rinzwind May 22 '15 at 16:46
  • 1
    What a brilliant answer! Okay, it's ultimately useless, but still very impressive, so +1 from me. – Carl H Jun 2 '15 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.