Ao desenvolver um projeto, tornou-se necessário excluir os arquivos criados pelo aplicativo durante sua execução. Mas era necessário que os arquivos não fossem excluídos no final da sessão, o trabalho do PC, mas a pedido do usuário.
E parece que não há problema. A biblioteca padrão Java possui um método delete () na classe java.io.File para excluir um arquivo.
File file = new File("path/to/file"); if (file.delete()) { System.out.println(file.getName() + " deleted"); } else { System.out.println(file.getName() + " not deleted"); }
O método delete () na classe java.io.File chama sob o capô uma função nativa para excluir um arquivo, dependendo do sistema operacional. E os sistemas operacionais modernos não excluem o arquivo imediatamente ao excluir um arquivo, mas excluem apenas o nome do arquivo. O conteúdo do arquivo permanece e a memória ocupada pelo arquivo já excluído pode ser reutilizada no futuro. Mesmo assim, por algum tempo, parece que o arquivo já excluído está disponível.
E se você olhar para a Internet, existem muitos programas para recuperar arquivos excluídos, por exemplo, o
Recuva .
Mas quero que os arquivos sejam excluídos sem a possibilidade de restaurá-los no futuro. Começando a pesquisar na Internet, acaba excluindo o arquivo sem restaurar (substituir) não é uma tarefa muito trivial. E, ao implementar essa tarefa, é necessário levar em consideração os recursos de trabalhar com arquivos em um SO específico. E, nesse caso, você precisa usar uma API nativa escrita manualmente ou algum tipo de biblioteca nativa.
Desde que o aplicativo foi desenvolvido no Ubuntu, este sistema operacional fornece muitas soluções prontas na forma de utilitários de linha de comando. Por exemplo, o utilitário de exclusão segura, que permite excluir arquivos sem recuperação usando abordagens diferentes.
$ srm -vz private/*
Os aplicativos devem verificar se o utilitário está instalado e exibir um erro se não o encontrar. Mas, no caso de usar o aplicativo para outro sistema operacional, você precisa usar um utilitário semelhante.
O que não é muito conveniente e quero me afastar desses problemas. Se você olhar para o código-fonte do utilitário de exclusão segura, ele permitirá que você trabalhe em diferentes sistemas operacionais. Está escrito em C99 e usa magia de pré-processamento diferente e uma API específica da plataforma. Depurar esse código nativo em caso de erro é muito difícil e essa é a tarefa.
Se você entender como o utilitário de exclusão segura funciona, as seguintes etapas podem ser distinguidas.
- Primeiro, ele verifica se o arquivo existe e se os direitos estão corretos.
- dependendo do algoritmo especificado, substitui o conteúdo do arquivo.
- reduz o tamanho do arquivo para zero bytes.
- renomeia o arquivo como uma sequência aleatória de caracteres.
- exclui o arquivo.
A exclusão segura permite que algoritmos diferentes substituam o conteúdo de um arquivo:
- Algoritmo simples - sobrescreve com 1 passagem de 0x00 bytes.
- Algoritmo DOE - sobrescreve com 3 passagens aleatórias, aleatórias, "DoE".
- Algoritmo RCMP - substitui com 3 passagens 0x00, 0xFF, "RCMP".
- Algoritmo OPENBSD - sobrescreve com 3 passagens bytes 0xFF, 0x00, 0xFF.
- Algoritmo DOD - substitui 7 passagens.
- Algoritmo de Gutmann - Substitui 35 passagens.
Gostaria que o código fosse independente de plataforma e funcionasse em diferentes sistemas operacionais. Se você observar o C ++ moderno, todas as etapas que a exclusão segura executa para substituir arquivos pode ser realizada.
Para verificar se um arquivo existe e se possui os direitos corretos, você pode usar o std :: filesystem, que foi adicionado no C ++ 17.
Para versões anteriores do padrão, boost :: filesystem pode ser usado.
namespace fs = std::filesystem; if (!fs::exists(file)) { env->ThrowNew(exception_class, "File doesn't exist"); } if (!fs::is_regular_file(file)) { env->ThrowNew(exception_class, "Path doesn't regular file or symlink"); } if (!eraser.check_permision(file, fs::status(file).permissions())) { env->ThrowNew(exception_class, "File hasn't enough permision (maybe not user)"); } bool kl::erase_content::check_permision(const fs::path& entry, fs::perms permision) { try { fs::permissions(entry, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::add); return true; } catch (fs::filesystem_error& e) { return false; } }
Para substituir o conteúdo do arquivo, dependendo do algoritmo escolhido, você pode deixar a implementação como em exclusão segura.
bool kl::erase_content::overwrite() { switch (entry.get_mode()) { case kl::overwrite_mode::SIMPLE_MODE: if (!overwrite_byte(1, 0x00)) { return false; } break; case kl::overwrite_mode::DOE_MODE: if (!overwrite_random(1)) { return false; } if (!overwrite_random(2)) { return false; } if (!overwrite_bytes(3, "DoE")) { return false; } break; case kl::overwrite_mode::OPENBSD_MODE: break; case kl::overwrite_mode::RCMP_MODE: break; case kl::overwrite_mode::DOD_MODE: break; case kl::overwrite_mode::GUTMAN_MODE: break; default: std::cerr << "overwrite mode doesn't choose" << std::endl; } return true; }
Um buffer de um determinado tamanho é preenchido com um determinado conjunto de dados, dependendo do algoritmo, e grava esse buffer em um arquivo até o final.
bool kl::erase_content::overwrite_byte(const int pass, const uint8_t byte) { const auto& [file_name, file_size, buffer_size, mode] = entry; this->buffer = std::make_unique<uint8_t[]>(buffer_size); std::memset(buffer.get(), byte, buffer_size); this->file = kl::fs_util::make_open_file(file_name, "r+b"); if (!overwrite_data(pass)) { return false; } return true; } bool kl::erase_content::overwrite_data(const int pass) { const auto& [file_name, file_size, buffer_size, mode] = entry; const size_t count = file_size / buffer_size; const size_t tail = file_size % buffer_size; size_t writted = 0; if (fseek(file.get(), 0, SEEK_SET) != 0) { std::cerr << "couldn't seek in file" << std::endl; return false; } writted = write_buffer(count, tail); if (writted != file_size) { std::cerr << "couldn't write buffer in file" << std::endl; return false; } fflush(file.get()); if (fseek(file.get(), 0, SEEK_SET) != 0) { std::cerr << "couldn't seek in file" << std::endl; return false; } file.reset(); return true; }
Em seguida, reduza o tamanho do arquivo para zero bytes, usando a função std :: filesystem :: resize_file () para isso.
try { fs::resize_file(file, 0); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "truncate file fail"); }
A próxima etapa é renomear o arquivo como uma sequência aleatória de caracteres, usando std :: random () e std :: filesystem :: file :: replace_filename () para isso.
std::string parent_path = file.parent_path(); std::string file_name = file.filename(); fs::path copy_file = file; file_name = random_text(file_name.size()); copy_file.replace_filename(fs::path(file_name)); try { fs::rename(file, copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't rename file"); } return true;
E na fase final, você só precisa excluir o arquivo usando std :: filesystem :: remove () para isso.
try { fs::remove(copy_file); } catch (fs::filesystem_error& e) { env->ThrowNew(exception_class, "can't remove file"); }
Bem, para uso em Java, você precisa declarar métodos nativos.
public class EraseFS { static { System.loadLibrary("jefl"); } public static native boolean eraseFile(String path) throws EraseException; public static native boolean eraseFile(String path, OverwrideMode mode) throws EraseException; public static native boolean eraseFiles(String... paths) throws EraseException; public static native boolean eraseFiles(OverwrideMode mode, String... paths) throws EraseException; public static native boolean eraseDirectory(String path, boolean recursived) throws EraseException; public static native boolean eraseDirectory(String path, OverwrideMode mode, boolean recursived) throws EraseException; }
A implementação nativa usa apenas a biblioteca C ++ padrão, o que facilita a porta para outras plataformas. E o mais importante, não existe mágica de macro do pré-processador, o que não é tão fácil de depurar em caso de erros.
O C ++ 17 padrão já é suportado por todos os compiladores populares: MVSC, Clang, GCC.
O código fonte completo pode ser visualizado no github:
code .