0

The other day I had a script error which wrote 4 million small text files to my home directory:

I've accidentally written 4 million small text files to a folder, how best to get rid of them?

I deleted those files, but since then whenever I hit tab to complete a filename or path there's a half second delay before anything happens.

Although the files are now deleted, I assume there's some lasting damage to the gpt or similar? Are there any useful tools I can use to clean this up?

The filesystem is ext4 (two 3TB drives in RAID 1) and I'm running CentOS 7.

% ls -ld "$HOME"
drwx------. 8 myname myname 363606016 Nov 18 09:21 /home/myname 

Thank you

LustreOne
  • 1,774
Codemonkey
  • 303
  • 3
  • 10
  • 3
    On some filesystems, the directory node is not rewritten when files are deleted in it. This means it may grow over time if you add many files to the directory. I don't know if this applies here. What size does ls -ld report on the directory itself, and does it help if you recreate the directory (create a now one, move all contents to this new directory, and remove the old)? – Kusalananda Nov 18 '19 at 09:35
  • Aye, it's huge - 360MB when there's hardly anything in it. – Codemonkey Nov 18 '19 at 10:14
  • It's my home directory though, with .bash_profile etc in it - so I'm not sure how to go about deleting/recreating it without possibly damaging my ability to log in etc! – Codemonkey Nov 18 '19 at 10:14
  • Ah, yes, that would make it a bit more tricky. And, as I said, I don't know if this is what's causing the issue you are experiencing. What does ls -ld "$HOME" output? – Kusalananda Nov 18 '19 at 11:10
  • drwx------. 8 myname myname 363606016 Nov 18 09:21 /home/myname – Codemonkey Nov 18 '19 at 13:06
  • Yeah, that's an issue. 360+ megabytes has to be read when scanning the directory contents, and it would likely destroy some other disk cache performance. – Kusalananda Nov 18 '19 at 13:13
  • There’s another option here https://unix.stackexchange.com/questions/38639/how-to-compact-a-directory – bxm Nov 18 '19 at 20:46

3 Answers3

7

As mentioned in the comments, your home directory itself is huge, and won’t shrink again. Scanning your home directory’s contents will involve reading a lot of data, every single time (from cache or disk).

To fix this, you need to re-create your home directory:

  • log out, log in as root, and make sure no running process refers to your home directory:

    lsof /home/myname
    
  • copy your home directory:

    cd /home
    cp -al myname myname.new
    
  • rename your home directory out of the way:

    mv myname myname.old
    
  • rename your new home directory:

    mv myname.new myname
    

You can log back in now. Your shiny, new home directory will only occupy the space it really needs, and file operations should be as fast as you expect. cp -al ensures that all files are available under the new directory, but it uses hard links so that no additional space is taken (apart from the directory structure). Because of the hard links, any changes made to files in one of the directories are reflected in the other directory, but you can safely remove myname.old.

A similar approach can be used for any directory which used to contain a large number of files, although in most other cases you won’t need to log out first.

ilkkachu
  • 138,973
Stephen Kitt
  • 434,908
  • 2
    I looked, but I did not find a How safe is it to rename my home directory while I am currently logged in? here. It's worth a Q&A in its own right. The answer is non-trivial, especially when one takes into account GUI and TUI login. – JdeBP Nov 18 '19 at 14:59
  • Worked perfectly, thanks. – Codemonkey Jan 03 '20 at 12:11
2

As mentioned in the other answer, if you can easily recreate the directory, then this can be done without shutting the system down.

In other cases, where the number or size of files in the directory tree make it harder to just copy them to a new directory, you can also unmount the filesystem (or boot from a rescue disk if it is the root filesystem) and run e2fsck -fD /dev/sdX for the filesystem to optimize the directories (-D option). That will pack the directory entries into the minimum number of blocks without copying the file data.

LustreOne
  • 1,774
0

Stephen Kitt's answer is basically the right approach, but it should be done using mv rather than cp - there's no need to spend several minutes or hours copying all the files, when moving them will take almost no time at all. For example:

  1. logout as your user (ALL login instances, including any ssh sessions) and login as root.

    If your system is configured to prevent direct root login (e.g. because root has a disabled password), login as a different user (create one if needed and give it access to su or sudo), then get a root shell.

  2. There should be no need to make sure that nothing is running as your user or has open files under your home dir because the file handles (and the inode numbers) will NOT change unless your home directory has its own dedicated file-system (e.g. if /home/username is its own mount-point, and not just a sub-directory of /home).

    The actual move (in Step 3 below) should take only a fraction of a second (depending on the number of files and dirs in the top level of your home dir. which is all that needs to be moved) because it's a mv, not a cp - no data is copied, it's all done very quickly by renaming.

    It is extremely unlikely that anything will create new files (or try to open a file that hasn't been moved yet) during that short time.

    However, if you're paranoid, or if it is likely that a cron or at job etc could do that, then temporarily disable them. Remember to restart them afterwards.

    Same for any other running process (e.g. NFS or samba or mail delivery or an ftpd or whatever) which is likely to do that - kill them now and restart them later. BTW, you can kill all user-owned processes with, e.g., pkill -u username.

  3. mv everything in your home directory to a new home directory. e.g. if your home dir is /home/username, run the following as root:

    cd /home
    mv username username.old
    mkdir username
    
    # move the files and subdirectories to the new home
    # BTW, using `find` ensures that "hidden" dotfiles and dotdirs are moved
    # along with the non-hidden files & dirs.
    cd username.old
    find . -mindepth 1 -maxdepth 1 -exec mv {} ../username/ +
    cd ..
    
    # fix ownership and perms of the new home dir
    gid="$(getent passwd username | cut -d: -f4)"
    perms="$(stat --printf "%a" username.old)"
    chown "username:$gid" username
    chmod "$perms" username
    
    rmdir username.old
    

Note: the stat --printf ... above requires GNU stat. You said you're running Centos 7, so that's what you have.

For anyone with a similar problem on a non GNU system, you'll have to find some other way to duplicate the perms. FreeBSD's version of stat has similar capabilities but the options are different. Or just set the perms on the new dir manually - they're most likely to be 775 or 755 (possibly with set-gid, so 2775 or 2755), but verify that with ls -ld or stat or whatever.

cas
  • 78,579