Errno::EACCES upon deleting a file (on Windows 10) - ruby-on-rails

There are a number of similar questions, but none of the answers worked for me.
I'm running Rails 6.0.3.2, Ruby 2.6.6, and SQLite3 on Windows 10 version 2004 (19041.388). I followed the Getting Started guide on the official Rails site to install Ruby on Rails and and everything should be up to date.
I can delete the files normally, and I'm logged in with an administrator account — not that it should be necessary.
I'm new to Ruby and Rails, so detailed answers would be appreciated.
Code
Here's what causes the error:
def destroy
book = Book.find(params[:id])
begin
File.open(book.cover_url, 'w') do |f|
File.delete(f)
end
rescue Errno::ENOENT
end
book.destroy
redirect_to books_path
end
What this does it first delete the cover image for a book and then delete the book itself from the database.
Errors
The error screen:
If the picture doesn't load, here are the error messages:
Errno::EACCES in BooksController#destroy
Permission denied # apply2files - D:/Projects/Web/RoR/ecommerce/app/assets/images/covers/circles_scaling_anim_positioning.png
File.delete(f) is the culprit.
Attempted Solutions
The only actionable answer I could find for Windows was this,
which advocated adding a 'lib' gem, but it did not work at all.
I also tried changing file mode from 'w' to 'wb+', but that didn't
work either.
EDIT 2: As per Dave Newton's suggestion in the comments (if that's what he meant), I moved the image storage directory outside the 'app' folder; to 'public/uploads/covers'. It hasn't worked either.
EDIT 3: I copied the delete code to a new script in another directory entirely and tried it on a sample file. I got the same error. In other words, the problem is not with Rails but Ruby (or my OS).
I called rm on the file from the terminal and that worked just fine, so I don't know if it's a file permissions problem.
EDIT: I checked the file in question and though it still remains, it's 0 bytes large now, so I presume it was overwritten by empty data. However, the rest of the code that should've execuded on destroy — i.e. the destruction of the object in the databse — seems not to have run, because the object is still in there.

The file handling enforced by the operating system is different on Windows compared to most UNIXy operating systems (such as macOS, Linux, or the various BSDs).
On the UNIXy operating systems, the file entry in the filesystem is just a pointer to the "real" file stored on disk. There are other possible pointers, such as a file handle when the file is opened by a process, or a hardlink (i.e. a different filesystem entry pointing to the the exact same file). The file exists on disk as long as there is at least one valid pointer to that file.
As such, on UNIXy systems, you can delete (or rename / move) a file from the filesystem while it is opened from a process. The file itself will only actually be deleted once the last filehandle is closed.
Windows however is more strict in this regard by default. It will not allow to delete a file as long as there is any process which has a file handle on the file (i.e. if any process has opened the file). This explains why you can't delete the file while you have a filehandle on it
With that being said, since Ruby 2.3.0 there is a flag you can set when opening the file which instructs Windows to allow deleting (or moving) the file while it is opened:
# the flags for the normal 'w' mode
file_mode = IO::WRONLY | IO::CREAT | IO::TRUNC
# Add flags to allow deleting (or moving) the opened file
file_mode |= IO::BINARY | IO::SHARE_DELETE
File.open(book.cover_url, mode: file_mode) do |f|
File.delete(f)
end
Note that the IO::SHARE_DELETE flag is only taken into account for files opened in binary mode (rather than text mode) take this into account when working with this opened file. On UNIXy systems the IO::SHARE_DELETE is ignored.
There is some documentation of the various flags available.
As a final remark: I assume that your code is a shortened example where you have left out some code actually interacting with the opened file in addition to deleting it.
If all you want is to delete the file, there is no need to opened it first. Just delete it with File.delete(book.cover_url). Alternatively, if you you don't care for any errors (such as if the file doesn't exist in the first place, you can also use FileUtils.rm_f(book.cover_url).

Changed the code to this...
begin
File.delete(book.cover_url)
rescue Errno::ENOENT
end
...and it works now. No opening any files; just File.delete(URL).
If anyone knows why the original version didn't work, or why it's so commonly suggested, please post an answer or comment on this answer.

Related

How to move Active Storage data from one machine to another

Because reasons we are trying to move a system from one machine to another one. It has several files in the storage directory. I rsynced it (using -a) to a local environment to see if everything works, but turns out not all the files are available, some of them raise an exception:
Errno::ENOENT (No such file or directory # rb_file_s_mtime - /path/to/project/storage/as/df/asdfasdfasdfasdfasdf):
Of course I checked the routes and they exists. I've been reading a bit about how Active Storage works and I maybe the URLs are getting invalidated for some reason, but why some files work? 🧐 Why the exception mentions mtime? And more importantly, how can I do the migration smoothly?
Thanks in advance
So the problem is actually the filesystems + Active Record names 😰 You can consider this a corner case: My local machine runs macOS, while the server runs Linux, so if I had folders Vf and VF on Linux, on macOS they become one (whichever is downloaded firs). Active Storage relies on case-sensitive filenames, and that's why some of the files work fine, but others are not found

detect and remove all executable files in uploaded ZIP file

I am working on a web application using Rails which user can upload a zip file which contains its data/file/docs and etc. But I'm concerned with security right now, I want to scan the uploaded zip file and remove all kind of executable such exe, bash and etc how can I do this?
Edit: I am aware of clamav API for rails but it would only scan the file for malicious files not removing the executable, just imagine opening a wrong uploaded executable file in the server and the cost of this action server/business-wide!
First, it would be better and more robust to whitelist allowed file types, and not blacklist disallowed ones (eg. executables). So you should have a list of types you allow if that is possible in your application.
Then the question is how you determine the type of a file.
The trivial way is checking the file extension, but that's not very strong. It may still be good for a first check to avoid spending precious cpu time on further checks.
After that, you can use the filemagic database to quite reliably find the type of uploaded files. You have two options:
If your application runs on linux, you can call the file tool directly, something like filetype = `file -Ib #{filename}` to get the filetype. Note that filename in this example needs to be sanitized to avoid OS command injection!
If you want to support Windows too (or just want to avoid calling shell commands and have nicer code), you can use the ruby-filemagic gem:
require 'filemagic'
filename = 'yourfile.ext'
magic = FileMagic.new
filetype = magic.file(filename)
The problem with ruby-filemagic is that it's not maintained anymore, but it would probably still work fine to find executables.

Rails 4: Issue Sending .txt files from local machine to Windows Share

I'm currently creating .csv files from a SQL view and writing to
#{Rails.root}/public/
which works no problem. In addition, I need to write these generated files to a Windows share in the form of:
\\NAME-APP.enterprise.company.com\Files
I've tried Net::SCP.upload, Net::SFTP.start, FileUtils, rsync, and even Dir.entries('share url here)` just to see if I can see anything in the folder, which generally results in
No such file or directory # dir_initialize
I can map my local computer to the Windows share point, in the form of:
smb://NAME-APP.enterprise.company.com/Files
but manually dragging and dropping to there isn't an acceptable solution in this case.
Feel like I've hit a wall and may be overlooking something. Have stumbled across this post but to no avail: How do I address a UNC path in Ruby on Windows?
Any advice on this is greatly appreciated.
Edit:
FileUtils.cp_r('/Volumes/Macintosh HD/Users/davidpardy/development/ror/sbb/oct31week/1a/FST-Export/public/1538791_new.txt', '\\\\NAME-APP\\Files')
doesn't return an error, but doesn't upload the .txt file to Files.
The solution is not to use FileUtils.cp_r(source_file, 'smb://...') because smb://... only represents the server address, not the mount folder on your filesystem.
In the terminal, run the mount command to find the path of the mount folder, which is what you'll use in ruby, e.g., FileUtils.cp_r(source_file, '/Volumes/mount_folder_here...').

How to avoid intermittent Errno::ETXTBSY exceptions?

During part of a request in a Rails application, I copy a directory from one place to another, think of it like a working area. Sometimes this copy operation results in "Errno::ETXTBSY" exceptions being thrown. I can't seem to pin down the case that causes it, any tips to detect the case or avoid it altogether?
I've made sure the destination directory is uniquely named, so it shouldn't be a case of 2 processes attempting to write to the same place. Beyond that I'm out of ideas.
ETXTBSY means that you're trying to open for writing a file which is currently being executed as a program, or that you're trying to execute a file which is currently open for writing. Since you say you're copying files, not executing them it seems likely it's the former, not the later.
You say you're targeting a unique new destination, but my guess is that's not entirely true and you're actually targeting an existing directory and one of the files you're attempting to overwrite is currently open as an executable text segment of a running process.
You haven't posted any code, so it's hard to comment specifically. I suggest you add enough logging so you know exactly what file(s) are being processed and specifically, the source and destination path that throws the exception. Then you could use lsof to see what process may have that file open.
One way to avoid the problem if you are overwriting a currently open executable, is to first unlink the target file. The running process will still have the old inode mapped and proceed merrily using the deleted file, but your open for write will then create a new file which won't conflict.

Does Windows.CopyFile create a temporary local file while source and destination are network shares?

I have a D2007 application that uses Windows.CopyFile to copy MS Word and PowerPoint files from one network folder to another network folder. Our organization is migrating to Windows 7 from Vista. One of my migrated users got an error message that displayed a partial local folder (C:\Users\(username)\...\A100203.doc) during the copy. Does the CopyFile function cache a local copy of the document when it is copying from one network folder to another network folder or is it a direct write? I have never seen this error before and the application has been running for years on Win95, Win 98, Win2000, WinXP and Vista.
Windows.CopyFile does NOT cache the file on your hard drive... instead, it instructs Windows to handle the copying of the file itself (rather than you managing the streams in your own program). The output file buffer (destination) is opened, and the input buffer simply read and written. Essentially this means that the source file is spooled into system memory, then offloaded onto the destination... at no point is an additional cache file created (this would slow file copying down).
You need to provide more specific information about your error... such as either the text or an actual screenshot of the offending error message. This will allow people to provide more useful answers.
The user that launches the copy will require read access to the original and write access to the target, regardless of caching (if the user has read access to the file, then the file can be written to a local cache, so caching/no-caching is irrelevant).
It's basic security to disallow someone to be able to copy files/directories among machines just because the security attributes between the machines are compatible.
There's little else to say without the complete text of the error message.

Resources