I know many of you have been sitting on Santa's lap wishing for more Command Line Kung Fu. Well, we've heard your pleas and are pushing one last Episode out before the New Year!
We come bearing a solution for a problem we've all encountered. Ever try to delete or modify a file and receive an error message that the file is in use? Of course you have! The real problem is trying to track down the user and/or process that has the file locked.
I have a solution for you on Windows, "openfiles". Well, sorta. This time of year I can't risk getting on Santa's bad side so let me add the disclaimer that it is only a partial solution. Here's what I mean, let's look for open files:
C:\> openfiles INFO: The system global flag 'maintain objects list' needs to be enabled to see local opened files. See Openfiles /? for more information. Files opened remotely via local share points: --------------------------------------------- INFO: No shared open files found.
By default when we run this command it gives us an error that we haven't enabled the feature. Wouldn't it be nice if we could simply turn it on and then look at the open files. Yes, it would be nice...but no. You have to reboot. This present is starting to look a lot like a lump of coal. So you need know that you will encounter the problem before it happens so you can be ready for it. Bah-Humbug!
To enable "openfile" run this command:
C:\> openfile /local on SUCCESS: The system global flag 'maintain objects list' is enabled. This will take effect after the system is restarted.
...then reboot.
Of course, now that we've rebooted the file will be unlocked, but we are prepared for next time. So next time when it happens we can run this command to see the results (note: if you don't specify a switch /query is implied):
C:\> openfiles /query Files Opened Locally: --------------------- ID Process Name Open File (Path\executable) ===== ==================== ================================================== 8 taskhostex.exe C:\Windows\System32 224 taskhostex.exe C:\Windows\System32\en-US\taskhostex.exe.mui 296 taskhostex.exe C:\Windows\Registration\R00000000000d.clb 324 taskhostex.exe C:\Windows\System32\en-US\MsCtfMonitor.dll.mui 752 taskhostex.exe C:\Windows\System32\en-US\winmm.dll.mui 784 taskhostex.exe C:\..\Local\Microsoft\Windows\WebCache\V01tmp.log 812 taskhostex.exe C:\Windows\System32\en-US\wdmaud.drv.mui ...
Of course, this is a quite long list. You can use use "find" or "findstr" to filter the results, but be aware that long file names are truncated (see ID 784). You can get a full list by changing the format with "/fo LIST". However, the file name will be on a separate line from the owning process and neither "find" nor "findstr" support context.
Another oddity, is that there seems to be duplicate IDs.
C:\> openfiles /query | find "888" 888 chrome.exe C:\Windows\Fonts\consola.ttf 888 Lenovo Transition.ex C:\..\Lenovo\Lenovo Transition\Gui\yo_btn_g3.png 888 vprintproxy.exe C:\Windows\Registration\R00000000000d.clb
Different processes with different files, all with the same ID. This means that when you disconnect the open file you better be careful.
Speaking of disconnecting the files, we can do just that with the /disconnect switch. We can disconnect by ID (ill advised) with the /id switch. We can also disconnect all the files based on the user:
C:\> openfiles /disconnect /a jacobmarley
Or the file name:
C:\> openfiles /disconnect /op "C:\Users\tm\Desktop\wishlist.txt" /a *
Or even the directory:
C:\> openfiles /disconnect /op "C:\Users\tm\Desktop\" /a *
We can even run this against a remote system with the /s SERVERNAME option.
This command is far from perfect, but it is pretty cool.
Sadly, there is no built-in capability in PowerShell to do this same thing. With PowerShell v4 we get Get-SmbOpenFile and Close-SmbOpenFile, but they only work on files opened over the network, not on files opened locally.
Now it is time for Mr. Scrooge Pomeranz to ruin my day by using some really useful, built-in, and ENABLED features of Linux.
It's a Happy Holiday for Hal:Awww, Tim got me the nicest present of all-- a super-easy Command-Line Kung Fu Episode to write!
This one's easy because Linux comes with lsof, a magical tool surely made by elves at the North Pole. I've talked about lsof in several other Episodes already but so far I've focused more on network and process-related queries than checking objects in the file system.
The simplest usage of lsof is checking which processes are using a single file:
# lsof /var/log/messages COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME rsyslogd 1250 root 1w REG 8,3 13779999 3146461 /var/log/messages abrt-dump 5293 root 4r REG 8,3 13779999 3146461 /var/log/messages
Here we've got two processes that have /var/log/messages open-- rsyslogd for writing (see the "1w" in the "FD" column, where the "w" means writing), and abrt-dump for reading ("4r", "r" for read-only).
You can use "lsof +d" to see all open files in a given directory:
# lsof +d /var/log COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME rsyslogd 1250 root 1w REG 8,3 14324534 3146461 /var/log/messages rsyslogd 1250 root 2w REG 8,3 175427 3146036 /var/log/cron rsyslogd 1250 root 5w REG 8,3 1644575 3146432 /var/log/maillog rsyslogd 1250 root 6w REG 8,3 2663 3146478 /var/log/secure abrt-dump 5293 root 4r REG 8,3 14324534 3146461 /var/log/messages
The funny thing about "lsof +d" is that it only shows you open files in the top-level directory, but not in any sub-directories. You have to use "lsof +D" for that:
# lsof +D /var/log COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME rsyslogd 1250 root 1w REG 8,3 14324534 3146461 /var/log/messages rsyslogd 1250 root 2w REG 8,3 175427 3146036 /var/log/cron rsyslogd 1250 root 5w REG 8,3 1644575 3146432 /var/log/maillog rsyslogd 1250 root 6w REG 8,3 2663 3146478 /var/log/secure httpd 3081 apache 2w REG 8,3 586 3146430 /var/log/httpd/error_log httpd 3081 apache 14w REG 8,3 0 3147331 /var/log/httpd/access_log ...
Unix-like operating systems track open files on a per-partition basis. This leads to an interesting corner-case with lsof: if you run lsof on a partition boundary, you get a list of all open files under that partition:
# lsof / COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME init 1 root cwd DIR 8,3 4096 2 / init 1 root rtd DIR 8,3 4096 2 / init 1 root txt REG 8,3 150352 12845094 /sbin/init init 1 root DEL REG 8,3 7340061 /lib64/libnss_files-2.12.so init 1 root DEL REG 8,3 7340104 /lib64/libc-2.12.so ... # lsof / | wc -l 3500
Unlike Windows, Linux doesn't really have a notion of disconnecting processes from individual files. If you want a process to release a file, you kill the process. lsof has a "-t" flag for terse output. In this mode, it only outputs the PIDs of the matching processes. This was designed to allow you to easily substitute the output of lsof as the arguments to the kill command. Here's the little trick I showed back in Episode 22 for forcibly unmounting a file system:
# umount /home umount: /home: device is busy # kill $(lsof -t /home) # umount /home
Here we're exploiting the fact that /home is a partition mount point so lsof will list all processes with files open anywhere in the file system. Kill all those processes and Santa might leave coal in your stocking next year, but you'll be able to unmount the file system!