From 26e5cb98949a83cacd1d3956e09aa472935e42f1 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Tue, 9 Dec 2025 10:40:06 +0100 Subject: [PATCH 1/3] feat(pdf): automatically create PDF report directory if missing and improve permission model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit enhances the handling of the `-P /path` / `--PDFreportpath` option by ensuring that nwipe can create the specified directory if it does not already exist. Previously, nwipe simply called `access(path, W_OK)` and exited with a generic “not a writeable directory” error if the directory did not exist or was not writable. This caused ambiguity and prevented the use of custom report paths without pre-creating them externally. Key improvements: - Added a new helper function `nwipe_ensure_directory()` that: - Differentiates between “non-existent”, “not a directory”, and “not writable”. - Attempts to create the directory recursively (`mkdir -p` style) when absent. - Creates directories with mode 0755 so other users can read/list directory contents. - Performs final verification that the directory exists, is a directory, and writable. - Replaced the previous simple `access()` check in `nwipe.c` with the new directory-ensure logic. - Introduces clearer and more helpful error messages when directory creation or permission checks fail. Benefits: - Users can now safely specify custom report paths (e.g. `-P /reports` or USB-mounted paths) without requiring manual pre-creation. - Eliminates ambiguous error reporting and improves overall user experience. - Maintains backward-compatible behavior when the target directory already exists. --- src/nwipe.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 3 deletions(-) diff --git a/src/nwipe.c b/src/nwipe.c index 96cc58b..f25cf76 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -77,6 +77,9 @@ #endif #endif +#define NWIPE_PDF_DIR_MODE ( S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) +/* -> 0755: rwx for owner, r-x for group and others */ + int terminate_signal; int user_abort; int global_wipe_status; @@ -96,6 +99,135 @@ int devnamecmp( const void* a, const void* b ) return ( ret ); } +static int nwipe_ensure_directory( const char* path ) +{ + struct stat st; + char* tmp; + char* p; + size_t len; + + if( path == NULL || path[0] == '\0' ) + { + /* Empty path: nothing to do, treat as success. */ + return 0; + } + + /* Special case: current directory. + * Historically nwipe called access(".", W_OK), so we keep that behavior. */ + if( strcmp( path, "." ) == 0 ) + { + if( access( path, W_OK ) != 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable: %s.", path, strerror( errno ) ); + return -1; + } + return 0; + } + + /* 1. First try: does the path already exist? */ + if( stat( path, &st ) == 0 ) + { + /* Path exists; make sure it's a directory. */ + if( !S_ISDIR( st.st_mode ) ) + { + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' exists but is not a directory.", path ); + return -1; + } + + /* Check that we can write into it. */ + if( access( path, W_OK ) != 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable: %s.", path, strerror( errno ) ); + return -1; + } + + /* Everything is fine, directory already present and writable. */ + return 0; + } + + /* stat() failed: if this is not "does not exist", propagate the error. */ + if( errno != ENOENT ) + { + nwipe_log( NWIPE_LOG_ERROR, "Failed to stat PDFreportpath '%s': %s.", path, strerror( errno ) ); + return -1; + } + + /* 2. Directory does not exist -> create it recursively (mkdir -p style). */ + + len = strlen( path ); + tmp = (char*) malloc( len + 1 ); + if( tmp == NULL ) + { + nwipe_log( NWIPE_LOG_ERROR, "Failed to allocate memory to create PDFreportpath '%s'.", path ); + return -1; + } + + memcpy( tmp, path, len + 1 ); + + /* Start at the beginning of the string. + * For absolute paths ("/foo/bar") we skip the leading slash so we do not + * try to create "/" itself. */ + p = tmp; + if( tmp[0] == '/' ) + { + p = tmp + 1; + } + + for( ; *p; ++p ) + { + if( *p == '/' ) + { + *p = '\0'; + + /* Skip empty components (can happen with leading '/' or double '//'). */ + if( tmp[0] != '\0' ) + { + if( mkdir( tmp, NWIPE_PDF_DIR_MODE ) != 0 && errno != EEXIST ) + { + nwipe_log( NWIPE_LOG_ERROR, + "Failed to create directory '%s' for PDFreportpath '%s': %s.", + tmp, + path, + strerror( errno ) ); + free( tmp ); + return -1; + } + } + + *p = '/'; + } + } + + /* Create the final directory component (the full path). */ + if( mkdir( tmp, NWIPE_PDF_DIR_MODE ) != 0 && errno != EEXIST ) + { + nwipe_log( NWIPE_LOG_ERROR, + "Failed to create directory '%s' for PDFreportpath '%s': %s.", + tmp, + path, + strerror( errno ) ); + free( tmp ); + return -1; + } + + free( tmp ); + + /* 3. Final sanity check: ensure the path now exists, is a directory and writable. */ + if( stat( path, &st ) != 0 || !S_ISDIR( st.st_mode ) ) + { + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' could not be verified as a directory after creation.", path ); + return -1; + } + + if( access( path, W_OK ) != 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable after creation: %s.", path, strerror( errno ) ); + return -1; + } + + return 0; +} + int main( int argc, char** argv ) { int nwipe_optind; // The result of nwipe_options(). @@ -198,12 +330,14 @@ int main( int argc, char** argv ) } } - /* Check if the given path for PDF reports is a writeable directory */ + /* Check if the given path for PDF reports is a writeable directory. + * If it does not exist, try to create it (mkdir -p style). + */ if( strcmp( nwipe_options.PDFreportpath, "noPDF" ) != 0 ) { - if( access( nwipe_options.PDFreportpath, W_OK ) != 0 ) + if( nwipe_ensure_directory( nwipe_options.PDFreportpath ) != 0 ) { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath %s is not a writeable directory.", nwipe_options.PDFreportpath ); + /* nwipe_ensure_directory already logged a detailed error message. */ cleanup(); exit( 2 ); } From c235349288650224e6ec7651b0bd562bab54ae28 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Tue, 9 Dec 2025 16:19:32 +0100 Subject: [PATCH 2/3] Added testfile probe to check if destination directory is writable --- src/nwipe.c | 115 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/src/nwipe.c b/src/nwipe.c index f25cf76..c4eb688 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -77,9 +77,6 @@ #endif #endif -#define NWIPE_PDF_DIR_MODE ( S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) -/* -> 0755: rwx for owner, r-x for group and others */ - int terminate_signal; int user_abort; int global_wipe_status; @@ -99,11 +96,62 @@ int devnamecmp( const void* a, const void* b ) return ( ret ); } -static int nwipe_ensure_directory( const char* path ) +#define NWIPE_PDF_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +/* -> 0755: rwx for owner, r-x for group and others */ + +/* Helper: try to create and remove a temporary file inside the directory. + * This catches cases where access(path, W_OK) passes (especially as root) + * but the underlying filesystem does not allow creating regular files, + * e.g. /proc or other pseudo/readonly filesystems. + */ +static int nwipe_probe_directory_writable( const char *path ) +{ + const char *suffix = "/.nwipe_pdf_testXXXXXX"; + size_t path_len = strlen( path ); + size_t suffix_len = strlen( suffix ); + size_t total_len = path_len + suffix_len + 1; /* +1 for '\0' */ + + char *tmpl = (char *)malloc( total_len ); + if( tmpl == NULL ) + { + nwipe_log( NWIPE_LOG_ERROR, + "Failed to allocate memory to probe PDFreportpath '%s'.", + path ); + return -1; + } + + /* Build template "/.nwipe_pdf_testXXXXXX" */ + snprintf( tmpl, total_len, "%s%s", path, suffix ); + + int fd = mkstemp( tmpl ); + if( fd < 0 ) + { + nwipe_log( NWIPE_LOG_ERROR, + "PDFreportpath '%s' is not writable (cannot create test file): %s.", + path, strerror( errno ) ); + free( tmpl ); + return -1; + } + + /* Successfully created a temporary file, now clean it up. */ + close( fd ); + if( unlink( tmpl ) != 0 ) + { + /* Not fatal for our check, but log it anyway. */ + nwipe_log( NWIPE_LOG_WARNING, + "Failed to remove temporary test file '%s' in PDFreportpath '%s': %s.", + tmpl, path, strerror( errno ) ); + } + + free( tmpl ); + return 0; +} + +static int nwipe_ensure_directory( const char *path ) { struct stat st; - char* tmp; - char* p; + char *tmp; + char *p; size_t len; if( path == NULL || path[0] == '\0' ) @@ -112,32 +160,23 @@ static int nwipe_ensure_directory( const char* path ) return 0; } - /* Special case: current directory. - * Historically nwipe called access(".", W_OK), so we keep that behavior. */ - if( strcmp( path, "." ) == 0 ) - { - if( access( path, W_OK ) != 0 ) - { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable: %s.", path, strerror( errno ) ); - return -1; - } - return 0; - } - /* 1. First try: does the path already exist? */ if( stat( path, &st ) == 0 ) { /* Path exists; make sure it's a directory. */ if( !S_ISDIR( st.st_mode ) ) { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' exists but is not a directory.", path ); + nwipe_log( NWIPE_LOG_ERROR, + "PDFreportpath '%s' exists but is not a directory.", + path ); return -1; } - /* Check that we can write into it. */ - if( access( path, W_OK ) != 0 ) + /* Even if access() says it's writable (especially as root), + * we still probe by actually creating a test file. */ + if( nwipe_probe_directory_writable( path ) != 0 ) { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable: %s.", path, strerror( errno ) ); + /* Detailed error already logged. */ return -1; } @@ -148,17 +187,21 @@ static int nwipe_ensure_directory( const char* path ) /* stat() failed: if this is not "does not exist", propagate the error. */ if( errno != ENOENT ) { - nwipe_log( NWIPE_LOG_ERROR, "Failed to stat PDFreportpath '%s': %s.", path, strerror( errno ) ); + nwipe_log( NWIPE_LOG_ERROR, + "Failed to stat PDFreportpath '%s': %s.", + path, strerror( errno ) ); return -1; } /* 2. Directory does not exist -> create it recursively (mkdir -p style). */ len = strlen( path ); - tmp = (char*) malloc( len + 1 ); + tmp = (char *)malloc( len + 1 ); if( tmp == NULL ) { - nwipe_log( NWIPE_LOG_ERROR, "Failed to allocate memory to create PDFreportpath '%s'.", path ); + nwipe_log( NWIPE_LOG_ERROR, + "Failed to allocate memory to create PDFreportpath '%s'.", + path ); return -1; } @@ -186,9 +229,7 @@ static int nwipe_ensure_directory( const char* path ) { nwipe_log( NWIPE_LOG_ERROR, "Failed to create directory '%s' for PDFreportpath '%s': %s.", - tmp, - path, - strerror( errno ) ); + tmp, path, strerror( errno ) ); free( tmp ); return -1; } @@ -203,25 +244,17 @@ static int nwipe_ensure_directory( const char* path ) { nwipe_log( NWIPE_LOG_ERROR, "Failed to create directory '%s' for PDFreportpath '%s': %s.", - tmp, - path, - strerror( errno ) ); + tmp, path, strerror( errno ) ); free( tmp ); - return -1; + return -1; } free( tmp ); - /* 3. Final sanity check: ensure the path now exists, is a directory and writable. */ - if( stat( path, &st ) != 0 || !S_ISDIR( st.st_mode ) ) + /* 3. Final sanity check: ensure the path is writable by probing with a file. */ + if( nwipe_probe_directory_writable( path ) != 0 ) { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' could not be verified as a directory after creation.", path ); - return -1; - } - - if( access( path, W_OK ) != 0 ) - { - nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable after creation: %s.", path, strerror( errno ) ); + /* Detailed error already logged. */ return -1; } From 1f68f35dc557331d8d4062f56aa41fff0e7c4565 Mon Sep 17 00:00:00 2001 From: Fabian Druschke Date: Tue, 9 Dec 2025 16:20:22 +0100 Subject: [PATCH 3/3] Fixed formatting. --- src/nwipe.c | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/nwipe.c b/src/nwipe.c index c4eb688..0e70cb2 100644 --- a/src/nwipe.c +++ b/src/nwipe.c @@ -96,7 +96,7 @@ int devnamecmp( const void* a, const void* b ) return ( ret ); } -#define NWIPE_PDF_DIR_MODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) +#define NWIPE_PDF_DIR_MODE ( S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) /* -> 0755: rwx for owner, r-x for group and others */ /* Helper: try to create and remove a temporary file inside the directory. @@ -104,19 +104,17 @@ int devnamecmp( const void* a, const void* b ) * but the underlying filesystem does not allow creating regular files, * e.g. /proc or other pseudo/readonly filesystems. */ -static int nwipe_probe_directory_writable( const char *path ) +static int nwipe_probe_directory_writable( const char* path ) { - const char *suffix = "/.nwipe_pdf_testXXXXXX"; + const char* suffix = "/.nwipe_pdf_testXXXXXX"; size_t path_len = strlen( path ); size_t suffix_len = strlen( suffix ); size_t total_len = path_len + suffix_len + 1; /* +1 for '\0' */ - char *tmpl = (char *)malloc( total_len ); + char* tmpl = (char*) malloc( total_len ); if( tmpl == NULL ) { - nwipe_log( NWIPE_LOG_ERROR, - "Failed to allocate memory to probe PDFreportpath '%s'.", - path ); + nwipe_log( NWIPE_LOG_ERROR, "Failed to allocate memory to probe PDFreportpath '%s'.", path ); return -1; } @@ -128,7 +126,8 @@ static int nwipe_probe_directory_writable( const char *path ) { nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' is not writable (cannot create test file): %s.", - path, strerror( errno ) ); + path, + strerror( errno ) ); free( tmpl ); return -1; } @@ -140,18 +139,20 @@ static int nwipe_probe_directory_writable( const char *path ) /* Not fatal for our check, but log it anyway. */ nwipe_log( NWIPE_LOG_WARNING, "Failed to remove temporary test file '%s' in PDFreportpath '%s': %s.", - tmpl, path, strerror( errno ) ); + tmpl, + path, + strerror( errno ) ); } free( tmpl ); return 0; } -static int nwipe_ensure_directory( const char *path ) +static int nwipe_ensure_directory( const char* path ) { struct stat st; - char *tmp; - char *p; + char* tmp; + char* p; size_t len; if( path == NULL || path[0] == '\0' ) @@ -166,9 +167,7 @@ static int nwipe_ensure_directory( const char *path ) /* Path exists; make sure it's a directory. */ if( !S_ISDIR( st.st_mode ) ) { - nwipe_log( NWIPE_LOG_ERROR, - "PDFreportpath '%s' exists but is not a directory.", - path ); + nwipe_log( NWIPE_LOG_ERROR, "PDFreportpath '%s' exists but is not a directory.", path ); return -1; } @@ -187,21 +186,17 @@ static int nwipe_ensure_directory( const char *path ) /* stat() failed: if this is not "does not exist", propagate the error. */ if( errno != ENOENT ) { - nwipe_log( NWIPE_LOG_ERROR, - "Failed to stat PDFreportpath '%s': %s.", - path, strerror( errno ) ); + nwipe_log( NWIPE_LOG_ERROR, "Failed to stat PDFreportpath '%s': %s.", path, strerror( errno ) ); return -1; } /* 2. Directory does not exist -> create it recursively (mkdir -p style). */ len = strlen( path ); - tmp = (char *)malloc( len + 1 ); + tmp = (char*) malloc( len + 1 ); if( tmp == NULL ) { - nwipe_log( NWIPE_LOG_ERROR, - "Failed to allocate memory to create PDFreportpath '%s'.", - path ); + nwipe_log( NWIPE_LOG_ERROR, "Failed to allocate memory to create PDFreportpath '%s'.", path ); return -1; } @@ -229,7 +224,9 @@ static int nwipe_ensure_directory( const char *path ) { nwipe_log( NWIPE_LOG_ERROR, "Failed to create directory '%s' for PDFreportpath '%s': %s.", - tmp, path, strerror( errno ) ); + tmp, + path, + strerror( errno ) ); free( tmp ); return -1; } @@ -244,9 +241,11 @@ static int nwipe_ensure_directory( const char *path ) { nwipe_log( NWIPE_LOG_ERROR, "Failed to create directory '%s' for PDFreportpath '%s': %s.", - tmp, path, strerror( errno ) ); + tmp, + path, + strerror( errno ) ); free( tmp ); - return -1; + return -1; } free( tmp );