1246 lines
35 KiB
C
1246 lines
35 KiB
C
/*
|
|
* Copyright (c) 2014, Peter Haag
|
|
* Copyright (c) 2009, Peter Haag
|
|
* Copyright (c) 2004-2008, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of the author nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* $Author: haag $
|
|
*
|
|
* $Id: flist.c 39 2009-11-25 08:11:15Z haag $
|
|
*
|
|
* $LastChangedRevision: 39 $
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/param.h>
|
|
#include <fcntl.h>
|
|
#include <ctype.h>
|
|
#include <netinet/in.h>
|
|
|
|
#ifdef HAVE_STDINT_H
|
|
# include <stdint.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_FTS_H
|
|
# include <fts.h>
|
|
#else
|
|
# include "fts_compat.h"
|
|
#define fts_children fts_children_compat
|
|
#define fts_close fts_close_compat
|
|
#define fts_open fts_open_compat
|
|
#define fts_read fts_read_compat
|
|
#define fts_set fts_set_compat
|
|
#endif
|
|
|
|
#include "nffile.h"
|
|
#include "util.h"
|
|
#include "flist.h"
|
|
|
|
/*
|
|
* Select a single file
|
|
* --------------------
|
|
* -r [/]path/to/single_file
|
|
* Select a single file: absolute or relativ path to a single file.
|
|
* Recursive: no
|
|
*
|
|
* Selecting a range of files
|
|
* --------------------------
|
|
* -R [/]path/to/first_file
|
|
* Select a range of files in directory specified by absolut or relative path [/]path/to/
|
|
* Files are selected in alphabetical order starting with 'first_file' to the end of
|
|
* the directory.
|
|
*
|
|
* -R [/]path/to/first_file:last_file
|
|
* Select a range of files in directory specified by absolut or relative path [/]path/to/
|
|
* Files are selected in alphabetical order starting with 'first_file' and ending with
|
|
* 'last_file'.
|
|
*
|
|
* -R [/]path/to/directory
|
|
* Select all files in alphabetical order in directory specified by absolut or relative
|
|
* path [/]path/to/directory
|
|
*
|
|
* Selecting files over multiple sources
|
|
* -------------------------------------
|
|
* -M /path/to/multiple/source1:source2[:..:sourceN]
|
|
* It is assumed, that each source directory 'source1', 'source2' etc. exists in directory
|
|
* /path/to/multiple. This will expand to multiple directories:
|
|
* /path/to/multiple/source1
|
|
* /path/to/multiple/source2
|
|
* ..
|
|
* /path/to/multiple/sourceN
|
|
* Each of these directories contain the same files.
|
|
* Used in combination with -r and -R to prepend file selections.
|
|
*
|
|
* Select a single file from multiple directories
|
|
* ----------------------------------------------
|
|
* -M /path/to/source1:source2 -r single_file
|
|
* Select the same file 'single_file' from each source directory: e.g.
|
|
* /path/to/source1/single_file
|
|
* /path/to/source2/single_file
|
|
*
|
|
*
|
|
* Select a range of files from multiple directories
|
|
* -------------------------------------------------
|
|
* -M /path/to/source1:source2[:..] -R first_file
|
|
* For each expanded directory specified by -M /path/to/source1:source2
|
|
* select a range of files as described above. Would be identical to
|
|
* -R /path/to/source1/first_file -R /path/to/source2/first_file
|
|
*
|
|
* -M /path/to/source1:source2[:..] -R first_file:last_file
|
|
* For each expanded directory specified by -M /path/to/source1:source2
|
|
* select a range of files as described above. Would be identical to
|
|
* -R /path/to/source1/first_file:last_file -R /path/to/source2/first_file:last_file [-R .. ]
|
|
*
|
|
* -M /path/to/source1:source2[:..] -R .
|
|
* For each expanded directory specified by -M /path/to/source1:source2
|
|
* select all files of the directory as described above. Would be to
|
|
* -R /path/to/source1 -R /path/to/source2 [-R ...]
|
|
*
|
|
*
|
|
* Hierarchical file organinisation:
|
|
* For performance reasons, files may be store in various sub directries instead of a
|
|
* single directory. These sub directories are assumed to be created in alpabetical order.
|
|
* For example daily sub directories: 2006/04/01 .. 2006/04/30 as created by nfcapd with
|
|
* option -S %y/%m/%d
|
|
*
|
|
* Single file selection is identical to the flat file layout:
|
|
* -r [/]path/to/sub1/sub2/sub3/single_file
|
|
*
|
|
* Selecting a range of files in a hierarchical file layout
|
|
* --------------------------------------------------------
|
|
* -R [/]path/to/sub1/sub2/first_file
|
|
* Select a range of files in directory specified by absolut or relative path
|
|
* [/]path/to/sub1/sub2/. Files are selected in alphabetical order starting with
|
|
* 'first_file' to the end of the directory. The hierarchy has no impact here.
|
|
*
|
|
* -R [/]path/to/first_sub1/first_sub2/first_file:last_sub1/last_sub2/last_file
|
|
* Select a range of files over multiple sub directories starting at absolut or
|
|
* relative path [/]path/to/first_sub1/first_sub2/first_file up to and including
|
|
* [/]path/to/last_sub1/last_sub2/last_file. Files are selected in alphabetical
|
|
* order by iterating over the required sub directory hierachy
|
|
* Example:
|
|
* -R /path/to/2006/03/31/nfcapd.200603312300:2006/04/01/nfcapd.200604010600
|
|
*
|
|
* -R [/]path/to/directory
|
|
* Select all files in alphabetical order in directory specified by absolut or relative
|
|
* path [/]path/to/directory, identical to flat layout
|
|
*
|
|
* The same methode applies for selecting a range of files over multiple sub directories
|
|
* and multiple sources.
|
|
*
|
|
* Example:
|
|
* -M /path/to/source1:source2 -R 2006/03/31/nfcapd.200603312300:2006/04/01/nfcapd.200604010600
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* syntax for possible sub dir definitions:
|
|
*
|
|
* %Y is replaced by the year with century as a decimal number.
|
|
* %y is replaced by the year without century as a decimal number (00-99).
|
|
* %m is replaced by the month as a decimal number (01-12).
|
|
* %d is replaced by the day of the month as a decimal number (01-31).
|
|
* %j is replaced by the day of the year as a decimal number (001-366).
|
|
* %H is replaced by the hour (24-hour clock) as a decimal number (00-23).
|
|
* %M is replaced by the minute as a decimal number (00-59).
|
|
* %s is replaced by the number of seconds since the Epoch, UTC
|
|
* %U is replaced by the week number of the year (Sunday as the first day
|
|
* of the week) as a decimal number (00-53).
|
|
* %W is replaced by the week number of the year (Monday as the first day
|
|
* of the week) as a decimal number (00-53).
|
|
* %w is replaced by the weekday (Sunday as the first day of the week) as
|
|
* a decimal number (0-6).
|
|
* %u is replaced by the weekday (Monday as the first day of the week) as
|
|
* a decimal number (1-7).
|
|
* %F is equivalent to ``%Y-%m-%d''.
|
|
*/
|
|
|
|
// predefined and accpeted formats
|
|
static const char *subdir_def[] = {
|
|
"", // default index 0 - no subdir hierarchy
|
|
"%Y/%m/%d",
|
|
"%Y/%m/%d/%H",
|
|
"%Y/%W/%u",
|
|
"%Y/%W/%u/%H",
|
|
"%Y/%j",
|
|
"%Y/%j/%H",
|
|
"%F",
|
|
"%F/%H",
|
|
NULL
|
|
};
|
|
|
|
|
|
// all accpeted char in a string
|
|
#define AcceptedFormatChar "YymdjHMsUWwuF"
|
|
|
|
static mode_t mode, dir_mode;
|
|
static const char *subdir_format;
|
|
|
|
static struct entry_filter_s {
|
|
char *first_entry;
|
|
char *last_entry;
|
|
int list_files;
|
|
} *dir_entry_filter;
|
|
|
|
#define NUM_PTR 16
|
|
|
|
// globals
|
|
extern uint32_t twin_first, twin_last;
|
|
|
|
static char *first_file, *last_file;
|
|
static char *current_file = NULL;
|
|
static stringlist_t source_dirs, file_list;
|
|
|
|
/* Function prototypes */
|
|
static inline int CheckTimeWindow(uint32_t t_start, uint32_t t_end, stat_record_t *stat_record);
|
|
|
|
static void GetFileList(char *path);
|
|
|
|
static void CleanPath(char *entry);
|
|
|
|
static void Getsource_dirs(char *dirs);
|
|
|
|
static int mkpath(char *path, char *p, mode_t mode, mode_t dir_mode, char *error, size_t errlen);
|
|
|
|
static char *GuessSubDir(char *channeldir, char *filename);
|
|
|
|
static char *VerifyFileRange(char *path, char *last_file);
|
|
|
|
/* Functions */
|
|
|
|
static int compare(const FTSENT **f1, const FTSENT **f2) {
|
|
return strcmp( (*f1)->fts_name, (*f2)->fts_name);
|
|
} // End of compare
|
|
|
|
static void CleanPath(char *entry) {
|
|
char *p, *q;
|
|
size_t len;
|
|
|
|
// wash out any '//' in entry
|
|
while ( (p = strstr(entry, "//")) != NULL ) {
|
|
p++;
|
|
q = p+1; // q points to first char after '//'
|
|
while ( *p )
|
|
*p++ = *q++;
|
|
}
|
|
|
|
// remove trailing '/'
|
|
len = strlen(entry);
|
|
if ( entry[len-1] == '/' )
|
|
entry[len-1] = '\0';
|
|
|
|
// wash out any '/./' in entry
|
|
while ( (p = strstr(entry, "/./")) != NULL ) {
|
|
p++;
|
|
q = p+2; // q points to first char after '/./'
|
|
while ( *p )
|
|
*p++ = *q++;
|
|
}
|
|
|
|
// remove leading './' in entry
|
|
if ( strstr(entry, "./") == entry ) {
|
|
p = entry;
|
|
q = p + 2;
|
|
while ( *p )
|
|
*p++ = *q++;
|
|
}
|
|
|
|
} // End of CleanPath
|
|
|
|
static inline int CheckTimeWindow(uint32_t t_start, uint32_t t_end, stat_record_t *stat_record) {
|
|
|
|
/*
|
|
printf("t start %u %s", t_start, ctime(&t_start));
|
|
printf("t end %u %s", t_end, ctime(&t_end));
|
|
printf("f start %u %s", NetflowStat.first_seen, ctime(&NetflowStat.first_seen));
|
|
printf("f end %u %s", NetflowStat.last_seen, ctime(&NetflowStat.last_seen));
|
|
*/
|
|
|
|
// if no time window is set, return true
|
|
if ( t_start == 0 )
|
|
return 1;
|
|
|
|
if ( stat_record->first_seen == 0 )
|
|
return 0;
|
|
|
|
if ( t_start >= stat_record->first_seen && t_start <= stat_record->last_seen )
|
|
return 1;
|
|
|
|
if ( t_end >= stat_record->first_seen && t_end <= stat_record->last_seen )
|
|
return 1;
|
|
|
|
if ( t_start < stat_record->first_seen && t_end > stat_record->last_seen )
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
} // End of CheckTimeWindow
|
|
|
|
// file filter for scandir function
|
|
|
|
static int dirlevels(char *dir) {
|
|
int num;
|
|
|
|
if ( !dir )
|
|
return 0;
|
|
|
|
num = 0;
|
|
if ( dir[0] == '/' )
|
|
dir++;
|
|
|
|
while ( *dir ) {
|
|
if ( *dir == '/' )
|
|
num++;
|
|
dir++;
|
|
}
|
|
|
|
return num;
|
|
|
|
} // End of dirlevels
|
|
|
|
static void CreateDirListFilter(char *first_path, char *last_path, int file_list_level) {
|
|
int i;
|
|
char *p, *q, *first_mark, *last_mark;
|
|
|
|
// printf("First Dir: '%s', first_path: '%s', last_path '%s', first_file '%s', last_file '%s', list_level: %i\n",
|
|
// source_dirs.list[0], first_path, last_path, first_file, last_file, file_list_level);
|
|
|
|
if ( file_list_level == 0 )
|
|
return;
|
|
|
|
if ( file_list_level < 0 ) {
|
|
fprintf(stderr, "software error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
|
|
exit(250);
|
|
}
|
|
|
|
dir_entry_filter = (struct entry_filter_s *)malloc((file_list_level+1) * sizeof(struct entry_filter_s));
|
|
if ( !dir_entry_filter ) {
|
|
fprintf(stderr, "malloc() error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
|
|
exit(250);
|
|
}
|
|
|
|
// first default entry - the directory itself
|
|
dir_entry_filter[0].first_entry = NULL;
|
|
dir_entry_filter[0].last_entry = NULL;
|
|
dir_entry_filter[0].list_files = 0;
|
|
|
|
first_mark = first_path;
|
|
last_mark = last_path;
|
|
// intermediate directory level filters
|
|
for ( i=1; i<file_list_level; i++ ) {
|
|
if ( first_mark ) {
|
|
p = strchr(first_mark, '/');
|
|
if ( p ) {
|
|
*p = '\0';
|
|
dir_entry_filter[i].first_entry = strdup(first_path);
|
|
*p++ = '/';
|
|
first_mark = p;
|
|
} else {
|
|
dir_entry_filter[i].first_entry = strdup(first_path);
|
|
first_mark = NULL;
|
|
}
|
|
} else {
|
|
dir_entry_filter[i].first_entry = NULL;
|
|
}
|
|
dir_entry_filter[i].list_files = 0;
|
|
|
|
if ( last_mark ) {
|
|
q = strchr(last_mark, '/');
|
|
if ( q ) {
|
|
*q = '\0';
|
|
dir_entry_filter[i].last_entry = strdup(last_path);
|
|
*q++ = '/';
|
|
last_mark = q;
|
|
} else {
|
|
dir_entry_filter[i].last_entry = strdup(last_path);
|
|
last_mark = NULL;
|
|
}
|
|
} else {
|
|
dir_entry_filter[i].last_entry = NULL;
|
|
}
|
|
if ( dir_entry_filter[i].first_entry && dir_entry_filter[i].last_entry &&
|
|
strcmp(dir_entry_filter[i].first_entry, dir_entry_filter[i].last_entry) > 0 )
|
|
fprintf(stderr, "WARNING: Entry '%s' > '%s'. Will not match anything!\n",
|
|
dir_entry_filter[i].first_entry, dir_entry_filter[i].last_entry);
|
|
|
|
// printf("%i first: '%s', last: '%s'\n",
|
|
// i, dir_entry_filter[i].first_entry, dir_entry_filter[i].last_entry);
|
|
}
|
|
|
|
// the last level - files are listed here
|
|
dir_entry_filter[file_list_level].first_entry = first_file;
|
|
dir_entry_filter[file_list_level].last_entry = last_file;
|
|
dir_entry_filter[file_list_level].list_files = 1;
|
|
|
|
if ( dir_entry_filter[file_list_level].first_entry && dir_entry_filter[file_list_level].last_entry &&
|
|
strcmp(dir_entry_filter[file_list_level].first_entry, dir_entry_filter[file_list_level].last_entry) > 0 )
|
|
fprintf(stderr, "WARNING: File '%s' > '%s'. Will not match anything!\n",
|
|
dir_entry_filter[file_list_level].first_entry, dir_entry_filter[file_list_level].last_entry);
|
|
|
|
// printf("%i first: '%s', last: '%s'\n",
|
|
// file_list_level, dir_entry_filter[file_list_level].first_entry, dir_entry_filter[file_list_level].last_entry);
|
|
|
|
} // End of CreateDirListFilter
|
|
|
|
static void GetFileList(char *path) {
|
|
struct stat stat_buf;
|
|
char *last_file_ptr, *first_path, *last_path;
|
|
int levels_first_file, levels_last_file, file_list_level;
|
|
int sub_index;
|
|
|
|
FTS *fts;
|
|
FTSENT *ftsent;
|
|
|
|
CleanPath(path);
|
|
|
|
// Check for last_file option
|
|
last_file_ptr = strchr(path, ':');
|
|
first_path = last_path = NULL;
|
|
levels_first_file = levels_last_file = 0;
|
|
if ( last_file_ptr ) {
|
|
// make sure we have only a single ':' in path
|
|
if ( strrchr(path, ':') != last_file_ptr ) {
|
|
fprintf(stderr, "Multiple file separators ':' in path not allowed!\n");
|
|
exit(250);
|
|
}
|
|
*last_file_ptr++ = '\0';
|
|
// last_file_ptr points to last_file
|
|
|
|
if ( strlen(last_file_ptr) == 0 ) {
|
|
fprintf(stderr, "Missing last file option after ':'!\n");
|
|
exit(250);
|
|
}
|
|
|
|
CleanPath(last_file_ptr);
|
|
// make sure last_file option is not a full path
|
|
if ( last_file_ptr[0] == '/') {
|
|
fprintf(stderr, "Last file name in -R list must not start with '/'\n");
|
|
exit(250);
|
|
}
|
|
// how may sub dir levels has last_file option?
|
|
levels_last_file = dirlevels(last_file_ptr);
|
|
|
|
// if no subdirs are given for last_file, try to find out, if the last_file
|
|
// exists in any possible subdirs
|
|
if ( levels_last_file == 0 ) {
|
|
char s[MAXPATHLEN];
|
|
char *r = VerifyFileRange(path, last_file_ptr);
|
|
|
|
if ( r != last_file_ptr && r[0] != '\0' ) {
|
|
snprintf(s, MAXPATHLEN-1, "%s/%s", r, last_file_ptr);
|
|
s[MAXPATHLEN-1] = '\0';
|
|
last_file_ptr = strdup(s);
|
|
levels_last_file = dirlevels(last_file_ptr);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
levels_first_file = dirlevels(path);
|
|
|
|
if ( source_dirs.num_strings == 0 ) {
|
|
// No multiple sources option -M
|
|
|
|
// path contains the path to a file/directory
|
|
// stat this entry
|
|
if ( stat(path, &stat_buf) ) {
|
|
fprintf(stderr, "stat() error '%s': %s\n", path, strerror(errno));
|
|
exit(250);
|
|
}
|
|
if ( !S_ISDIR(stat_buf.st_mode) && !S_ISREG(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Not a file or directory: '%s'\n", path);
|
|
exit(250);
|
|
}
|
|
|
|
// Check, how many levels of directory in path
|
|
levels_first_file = dirlevels(path);
|
|
|
|
if ( last_file_ptr ) {
|
|
// path is [/]path/to/any/dir|file:last_file_ptr
|
|
|
|
// make sure first_file is a file
|
|
if ( S_ISDIR(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Not a file: '%s'\n", path);
|
|
exit(250);
|
|
}
|
|
|
|
if ( levels_last_file ) {
|
|
// we have levels_last_file number of sub dirs
|
|
|
|
// sub dir levels of first_file mus have at least the same number of levels as last_file
|
|
if ( levels_first_file < levels_last_file ) {
|
|
fprintf(stderr, "Number of sub dirs for sub level hierarchy for file list -R do not match\n");
|
|
exit(250);
|
|
}
|
|
if ( levels_first_file == levels_last_file ) {
|
|
char *p, *q;
|
|
// path = [/]sub1[/..]/first_file:sub1[/...]/last_file
|
|
if ( path[0] == '/' ) {
|
|
// this is rather strange, but strctly spoken, valid anyway
|
|
InsertString(&source_dirs, "/");
|
|
path++;
|
|
} else {
|
|
InsertString(&source_dirs, ".");
|
|
}
|
|
|
|
// path = sub_first[/..]/first_file:sub_last[/...]/last_file
|
|
p = strrchr(path, '/');
|
|
q = strrchr(last_file_ptr, '/');
|
|
if ( !p || !q ) {
|
|
// this should never happen
|
|
fprintf(stderr, "software error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
|
|
exit(250);
|
|
}
|
|
*p++ = '\0';
|
|
*q++ = '\0';
|
|
first_file = strdup(p);
|
|
last_file = strdup(q);
|
|
file_list_level = levels_last_file + 1;
|
|
first_path = path;
|
|
last_path = last_file_ptr;
|
|
|
|
} else {
|
|
// path = [/]path/to/sub_first[/..]/first_file:sub_last[/...]/last_file
|
|
int i;
|
|
char *p, *r, *s;
|
|
|
|
p = strrchr(path, '/');
|
|
// levels_first_file > levels_last_file
|
|
|
|
// step back the number of sub dirs in first_file
|
|
for ( i=0; i<levels_last_file; i++ ) {
|
|
do {
|
|
p--;
|
|
} while ( p >= path && *p != '/');
|
|
}
|
|
*p++ = '\0';
|
|
|
|
InsertString(&source_dirs, path);
|
|
|
|
r = strrchr(p, '/');
|
|
s = strrchr(last_file_ptr, '/');
|
|
if ( !r || !s ) {
|
|
// this must never happen
|
|
fprintf(stderr, "software error in %s line %d: %s\n", __FILE__, __LINE__, strerror(errno) );
|
|
exit(250);
|
|
}
|
|
*r++ = '\0';
|
|
*s++ = '\0';
|
|
first_file = strdup(r);
|
|
last_file = strdup(s);
|
|
// files are listed at this sub dir level
|
|
file_list_level = levels_last_file + 1;
|
|
first_path = p;
|
|
last_path = last_file_ptr;
|
|
|
|
}
|
|
|
|
} else {
|
|
// we have no sub dir levels given
|
|
|
|
// path is [/]path/to/any/file
|
|
char *p = strrchr(path, '/');
|
|
|
|
if ( p ) {
|
|
// path is [/]path/to/any/first_file:last_file
|
|
*p++ = '\0';
|
|
// path is the direcory containing all the files
|
|
InsertString(&source_dirs, path);
|
|
first_file = strdup(p);
|
|
} else {
|
|
// path is first_file:last_file
|
|
InsertString(&source_dirs, ".");
|
|
first_file = strdup(path);
|
|
}
|
|
// set last_file filter
|
|
last_file = strdup(last_file_ptr);
|
|
// in any case we list the files of directory level 1
|
|
file_list_level = 1;
|
|
}
|
|
} else {
|
|
// path is [/]path/to/any/dir|file
|
|
if ( S_ISDIR(stat_buf.st_mode) ) {
|
|
// path is [/]path/to/any/dir
|
|
// list all files in this directory
|
|
InsertString(&source_dirs, path);
|
|
first_file = NULL;
|
|
file_list_level = 0;
|
|
} else {
|
|
// path is [/]path/to/any/file
|
|
char *p = strrchr(path, '/');
|
|
if ( p ) {
|
|
// path is [/]path/to/any/file
|
|
*p++ = '\0';
|
|
// path is the direcory containing all the files
|
|
InsertString(&source_dirs, path);
|
|
first_file = strdup(p);
|
|
} else {
|
|
// path is file
|
|
InsertString(&source_dirs, ".");
|
|
first_file = strdup(path);
|
|
}
|
|
// in any case we list the files of directory level 1
|
|
file_list_level = 1;
|
|
}
|
|
// in any case, no last_file filter
|
|
last_file = NULL;
|
|
}
|
|
|
|
} else {
|
|
char pathbuff[MAXPATHLEN];
|
|
// multiple sources option -M given
|
|
if ( path[0] == '/') {
|
|
fprintf(stderr, "File list -R must not start with '/' when combined with a source list -M\n");
|
|
exit(250);
|
|
}
|
|
|
|
// special case for all files in directory
|
|
if ( strcmp(path, ".") == 0 ) {
|
|
first_file = NULL;
|
|
last_file = NULL;
|
|
file_list_level = 0;
|
|
} else {
|
|
// pathbuff contains the path to a file/directory, compiled using the first entry
|
|
// in the source_dirs
|
|
snprintf(pathbuff, MAXPATHLEN-1, "%s/%s", source_dirs.list[0], path);
|
|
pathbuff[MAXPATHLEN-1] = '\0';
|
|
|
|
// pathbuff must point to a file
|
|
if ( stat(pathbuff, &stat_buf) ) {
|
|
if ( errno == ENOENT ) {
|
|
// file not found - try to guess a possible subdir
|
|
char *sub_dir = GuessSubDir(source_dirs.list[0], path);
|
|
if ( sub_dir ) { // subdir found
|
|
snprintf(pathbuff, MAXPATHLEN-1, "%s/%s", sub_dir, path);
|
|
pathbuff[MAXPATHLEN-1] = '\0';
|
|
// update path
|
|
path = strdup(pathbuff);
|
|
free(sub_dir);
|
|
|
|
// need guessing subdir with last_file too
|
|
if ( last_file_ptr ) {
|
|
sub_dir = GuessSubDir(source_dirs.list[0], last_file_ptr);
|
|
if ( sub_dir ) { // subdir found
|
|
snprintf(pathbuff, MAXPATHLEN-1, "%s/%s", sub_dir, last_file_ptr);
|
|
pathbuff[MAXPATHLEN-1] = '\0';
|
|
last_file_ptr = strdup(pathbuff);
|
|
free(sub_dir);
|
|
|
|
// update dir levels of extended file path
|
|
levels_last_file = dirlevels(last_file_ptr);
|
|
} else {
|
|
fprintf(stderr, "'%s': %s\n", last_file_ptr, "File not found!");
|
|
exit(250);
|
|
}
|
|
}
|
|
|
|
} else { // no file in any possible subdir found
|
|
fprintf(stderr, "stat() error '%s': %s\n", pathbuff, "File not found!");
|
|
exit(250);
|
|
}
|
|
} else { // Any other stat error
|
|
fprintf(stderr, "stat() error '%s': %s\n", pathbuff, strerror(errno));
|
|
exit(250);
|
|
}
|
|
} else if ( !S_ISREG(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Not a file : '%s'\n", pathbuff);
|
|
exit(250);
|
|
}
|
|
|
|
// Check, how many levels of directory in path
|
|
levels_first_file = dirlevels(path);
|
|
|
|
if ( last_file_ptr ) {
|
|
// path is path/to/any/first_file:last_file_ptr
|
|
char *p, *q;
|
|
|
|
// the number of sub dirs must be eqal for first_file and last_file
|
|
if ( levels_first_file != levels_last_file ) {
|
|
fprintf(stderr, "Number of sub dirs must agree in '%s' and '%s'\n", path, last_file_ptr);
|
|
exit(250);
|
|
}
|
|
|
|
p = strrchr(path, '/');
|
|
if ( p ) {
|
|
// path is fist_sub/to/any/first_file
|
|
// recursive all files in sub dirs
|
|
file_list_level = dirlevels(path) + 1;
|
|
*p++ = '\0';
|
|
first_file = strdup(p);
|
|
first_path = path;
|
|
} else {
|
|
// path is first_file
|
|
first_file = strdup(path);
|
|
file_list_level = 1;
|
|
}
|
|
|
|
q = strrchr(last_file_ptr, '/');
|
|
if ( q ) {
|
|
*q++ = '\0';
|
|
last_file = strdup(q);
|
|
last_path = last_file_ptr;
|
|
} else {
|
|
last_file = strdup(last_file_ptr);
|
|
}
|
|
|
|
} else {
|
|
// path is path/to/any/first_file
|
|
char *p = strrchr(path, '/');
|
|
if ( p ) {
|
|
// path is fist_sub/to/any/first_file
|
|
// recursive all files in sub dirs
|
|
file_list_level = dirlevels(path) + 1;
|
|
*p++ = '\0';
|
|
first_file = strdup(p);
|
|
first_path = path;
|
|
} else {
|
|
// path is first_file
|
|
first_file = strdup(path);
|
|
file_list_level = 1;
|
|
}
|
|
last_file = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
printf("first_file %s\n", first_file ? first_file : "<none>");
|
|
printf("last_file %s\n", last_file ? last_file : "<none>");
|
|
printf("first_path %s\n", first_path ? first_path : "<none>");
|
|
printf("last_path %s\n", last_path ? last_path : "<none>");
|
|
printf("file_list_level: %i\n", file_list_level);
|
|
*/
|
|
CreateDirListFilter(first_path, last_path, file_list_level );
|
|
|
|
// last entry must be NULL
|
|
InsertString(&source_dirs, NULL);
|
|
fts = fts_open(source_dirs.list, FTS_LOGICAL, compare);
|
|
sub_index = 0;
|
|
while ( (ftsent = fts_read(fts)) != NULL) {
|
|
int fts_level = ftsent->fts_level;
|
|
char *fts_path;
|
|
|
|
// printf("DBG: %u %i %s %s\n", ftsent->fts_info, ftsent->fts_level, ftsent->fts_path, ftsent->fts_name);
|
|
|
|
if ( fts_level == 0 ) {
|
|
sub_index = ftsent->fts_pathlen + 1;
|
|
continue;
|
|
}
|
|
|
|
if ( ftsent->fts_pathlen < sub_index ) {
|
|
printf("ERROR: fts_pathlen error at %s line %d\n", __FILE__, __LINE__);
|
|
exit(250);
|
|
}
|
|
fts_path = &ftsent->fts_path[sub_index];
|
|
|
|
/*
|
|
if ( file_list_level )
|
|
printf("DGB: short fts: '%s', filer_first: '%s', filter_last: '%s'\n",
|
|
fts_path, dir_entry_filter[fts_level].first_entry , dir_entry_filter[fts_level].last_entry);
|
|
*/
|
|
switch (ftsent->fts_info) {
|
|
case FTS_D:
|
|
// dir entry pre descend
|
|
if ( file_list_level && file_list_level && (
|
|
( dir_entry_filter[fts_level].first_entry &&
|
|
( strcmp(fts_path, dir_entry_filter[fts_level].first_entry ) < 0 ) ) ||
|
|
( dir_entry_filter[fts_level].last_entry &&
|
|
( strcmp(fts_path, dir_entry_filter[fts_level].last_entry ) > 0 ) )
|
|
))
|
|
fts_set(fts, ftsent, FTS_SKIP );
|
|
|
|
break;
|
|
case FTS_DP:
|
|
break;
|
|
case FTS_F:
|
|
// file entry
|
|
// printf("==> Check: %s\n", ftsent->fts_name);
|
|
|
|
// skip stat file
|
|
if ( strcmp(ftsent->fts_name, ".nfstat") == 0 ||
|
|
strncmp(ftsent->fts_name, NF_DUMPFILE , strlen(NF_DUMPFILE)) == 0)
|
|
continue;
|
|
if ( strstr(ftsent->fts_name, ".stat") != NULL )
|
|
continue;
|
|
// skip pcap file
|
|
if ( strstr(ftsent->fts_name, "pcap") != NULL )
|
|
continue;
|
|
|
|
if ( file_list_level && (
|
|
( fts_level != file_list_level ) ||
|
|
( dir_entry_filter[fts_level].first_entry &&
|
|
( strcmp(ftsent->fts_name, dir_entry_filter[fts_level].first_entry) < 0 ) ) ||
|
|
( dir_entry_filter[fts_level].last_entry &&
|
|
( strcmp(ftsent->fts_name, dir_entry_filter[fts_level].last_entry) > 0 ) )
|
|
) )
|
|
continue;
|
|
|
|
// printf("==> Listed: %s\n", ftsent->fts_path);
|
|
InsertString(&file_list, ftsent->fts_path);
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
fts_close(fts);
|
|
|
|
} // End of GetFileList
|
|
|
|
/*
|
|
* Get the list of directories
|
|
* dirs: user supplied parameter: /any/path/dir1:dir2:dir3:...
|
|
* source_dirs must result in
|
|
* /any/path/dir1
|
|
* /any/path/dir2
|
|
* /any/path/dir3
|
|
* /any/path is dir prefix, which may be NULL e.g. dir1:dir2:dir3:...
|
|
* dir1, dir2 etc entrys
|
|
*/
|
|
void Getsource_dirs(char *dirs) {
|
|
struct stat stat_buf;
|
|
char *p, *q, *dirprefix;
|
|
char path[MAXPATHLEN];
|
|
|
|
q = strchr(dirs, ':');
|
|
if ( q ) { // we have /path/to/firstdir:dir1:dir2:...
|
|
*q = 0;
|
|
p = strrchr(dirs, '/');
|
|
if ( p ) {
|
|
*p++ = 0; // p points now to the first name in the dir list
|
|
dirprefix = dirs;
|
|
} else { // we have a source_dirs in current directory
|
|
p = dirs; // p points now to the first name in the dir list
|
|
dirprefix = "."; // current directory
|
|
}
|
|
*q = ':'; // restore ':' in source_dirs
|
|
|
|
while ( p ) { // iterate over all elements in the dir list
|
|
q = strchr(p, ':');
|
|
if ( q )
|
|
*q = 0;
|
|
|
|
// p point to a dir name
|
|
snprintf(path, 1023, "%s/%s", dirprefix, p);
|
|
path[MAXPATHLEN-1] = 0;
|
|
if ( stat(dirs, &stat_buf) ) {
|
|
fprintf(stderr, "Can't stat '%s': %s\n", path, strerror(errno));
|
|
return;
|
|
}
|
|
if ( !S_ISDIR(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Not a directory: '%s'\n", path);
|
|
return;
|
|
}
|
|
|
|
// save path into source_dirs
|
|
InsertString(&source_dirs, path);
|
|
|
|
p = q ? q + 1 : NULL;
|
|
}
|
|
|
|
} else { // we have only one directory
|
|
if ( stat(dirs, &stat_buf) ) {
|
|
fprintf(stderr, "Can't stat '%s': %s\n", dirs, strerror(errno));
|
|
return;
|
|
}
|
|
if ( !S_ISDIR(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Not a directory: '%s'\n", dirs);
|
|
return;
|
|
}
|
|
|
|
// save the path into source_dirs
|
|
InsertString(&source_dirs, dirs);
|
|
}
|
|
|
|
} // End of Getsource_dirs
|
|
|
|
void SetupInputFileSequence(char *multiple_dirs, char *single_file, char *multiple_files) {
|
|
|
|
twin_first = 0;
|
|
twin_last = 0xffffffff;
|
|
|
|
first_file = NULL;
|
|
last_file = NULL;
|
|
|
|
InitStringlist(&source_dirs, NUM_PTR);
|
|
InitStringlist(&file_list, 64);
|
|
|
|
if ( multiple_dirs )
|
|
Getsource_dirs(multiple_dirs);
|
|
|
|
if ( multiple_files ) {
|
|
// use multiple files
|
|
GetFileList(multiple_files);
|
|
|
|
// get time window spanning all the files
|
|
if ( file_list.num_strings ) {
|
|
stat_record_t stat_ptr;
|
|
|
|
// read the stat record
|
|
if ( !GetStatRecord(file_list.list[0], &stat_ptr) ) {
|
|
exit(250);
|
|
}
|
|
twin_first = stat_ptr.first_seen;
|
|
|
|
// read the stat record of last file
|
|
if ( !GetStatRecord(file_list.list[file_list.num_strings-1], &stat_ptr) ) {
|
|
exit(250);
|
|
}
|
|
twin_last = stat_ptr.last_seen;
|
|
}
|
|
|
|
} else if ( single_file ) {
|
|
CleanPath(single_file);
|
|
|
|
if ( source_dirs.num_strings == 0 ) {
|
|
stat_record_t stat_ptr;
|
|
InsertString(&file_list, single_file);
|
|
if ( !GetStatRecord(single_file, &stat_ptr) ) {
|
|
exit(250);
|
|
}
|
|
twin_first = stat_ptr.first_seen;
|
|
twin_last = stat_ptr.last_seen;
|
|
|
|
} else {
|
|
int i;
|
|
|
|
if ( single_file[0] == '/' ) {
|
|
fprintf(stderr, "File -r must not start with '/', when combined with a source list -M\n");
|
|
exit(250);
|
|
}
|
|
|
|
for ( i=0; i<source_dirs.num_strings; i++ ) {
|
|
char s[MAXPATHLEN];
|
|
struct stat stat_buf;
|
|
|
|
snprintf(s, MAXPATHLEN-1, "%s/%s", source_dirs.list[i], single_file);
|
|
s[MAXPATHLEN-1] = '\0';
|
|
if ( stat(s, &stat_buf) ) {
|
|
if ( errno == ENOENT ) {
|
|
// file not found - try to guess subdir
|
|
char *sub_dir = GuessSubDir(source_dirs.list[i], single_file);
|
|
if ( sub_dir ) { // subdir found
|
|
stat_record_t stat_ptr;
|
|
snprintf(s, MAXPATHLEN-1, "%s/%s/%s", source_dirs.list[i], sub_dir, single_file);
|
|
s[MAXPATHLEN-1] = '\0';
|
|
InsertString(&file_list, s);
|
|
if ( !GetStatRecord(s, &stat_ptr) ) {
|
|
exit(250);
|
|
}
|
|
twin_first = stat_ptr.first_seen;
|
|
twin_last = stat_ptr.last_seen;
|
|
} else { // no subdir found
|
|
fprintf(stderr, "stat() error '%s': %s\n", s, "File not found!");
|
|
}
|
|
} else { // Any other stat error
|
|
fprintf(stderr, "stat() error '%s': %s\n", s, strerror(errno));
|
|
exit(250);
|
|
}
|
|
} else { // stat() successful
|
|
if ( !S_ISREG(stat_buf.st_mode) ) {
|
|
fprintf(stderr, "Skip non file entry: '%s'\n", s);
|
|
} else {
|
|
stat_record_t stat_ptr;
|
|
InsertString(&file_list, s);
|
|
if ( !GetStatRecord(s, &stat_ptr) ) {
|
|
exit(250);
|
|
}
|
|
twin_first = stat_ptr.first_seen;
|
|
twin_last = stat_ptr.last_seen;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else // else use stdin
|
|
InsertString(&file_list, NULL);
|
|
|
|
} // End of SetupInputFileSequence
|
|
|
|
char *GetCurrentFilename(void) {
|
|
return current_file;
|
|
} // End of GetCurrentFilename
|
|
|
|
nffile_t *GetNextFile(nffile_t *nffile, time_t twin_start, time_t twin_end) {
|
|
static int cnt;
|
|
|
|
// close current file before open the next one
|
|
// stdin ( current = 0 ) is not closed
|
|
if ( nffile ) {
|
|
CloseFile(nffile);
|
|
current_file = NULL;
|
|
} else {
|
|
// is it first time init ?
|
|
cnt = 0;
|
|
}
|
|
|
|
// no or no more files available
|
|
if ( file_list.num_strings == cnt ) {
|
|
current_file = NULL;
|
|
return EMPTY_LIST;
|
|
}
|
|
|
|
|
|
while ( cnt < file_list.num_strings ) {
|
|
#ifdef DEVEL
|
|
printf("Process: '%s'\n", file_list.list[cnt] ? file_list.list[cnt] : "<stdin>");
|
|
#endif
|
|
nffile = OpenFile(file_list.list[cnt], nffile); // Open the file
|
|
if ( !nffile ) {
|
|
return NULL;
|
|
}
|
|
current_file = file_list.list[cnt];
|
|
cnt++;
|
|
|
|
// stdin
|
|
if ( nffile->fd == STDIN_FILENO ) {
|
|
current_file = NULL;
|
|
return nffile;
|
|
}
|
|
|
|
if ( CheckTimeWindow(twin_start, twin_end, nffile->stat_record) ) {
|
|
// printf("Return file: %s\n", string);
|
|
return nffile;
|
|
}
|
|
CloseFile(nffile);
|
|
}
|
|
|
|
current_file = NULL;
|
|
return EMPTY_LIST;
|
|
|
|
} // End of GetNextFile
|
|
|
|
|
|
int InitHierPath(int num) {
|
|
int i;
|
|
|
|
subdir_format = NULL;
|
|
|
|
i=0;
|
|
while ( subdir_def[i] != NULL ) {
|
|
if ( i == num )
|
|
break;
|
|
i++;
|
|
}
|
|
if ( subdir_def[i] == NULL ) {
|
|
fprintf(stderr, "No such subdir level %i\n", num);
|
|
return 0;
|
|
}
|
|
|
|
subdir_format = subdir_def[i];
|
|
|
|
/*
|
|
* The default file mode is a=rwx (0777) with selected permissions
|
|
* removed in accordance with the file mode creation mask. For
|
|
* intermediate path name components, the mode is the default modified
|
|
* by u+wx so that the subdirectories can always be created.
|
|
*/
|
|
|
|
// get umask
|
|
mode = umask(0);
|
|
umask(mode);
|
|
|
|
mode = 0777 & ~mode;
|
|
dir_mode = mode | S_IWUSR | S_IXUSR;
|
|
|
|
return 1;
|
|
|
|
} // End of InitHierPath
|
|
|
|
static char *VerifyFileRange(char *path, char *last_file) {
|
|
char *p, *q, *r;
|
|
|
|
r = strdup(path);
|
|
p = strrchr(r, '/');
|
|
while ( p ) {
|
|
*p = '\0';
|
|
|
|
q = GuessSubDir(r, last_file);
|
|
if ( q ) {
|
|
free(r);
|
|
return q;
|
|
}
|
|
p = strrchr(r, '/');
|
|
}
|
|
|
|
free(r);
|
|
return last_file;
|
|
|
|
} // End of VerifyFileRange
|
|
|
|
static char *GuessSubDir(char *channeldir, char *filename) {
|
|
char s[MAXPATHLEN];
|
|
struct tm *t_tm;
|
|
int i;
|
|
|
|
if ( strlen(filename) == 19 && (strncmp(filename, "nfcapd.", 7) == 0) ) {
|
|
char *p = &filename[7];
|
|
time_t t = ISO2UNIX(p);
|
|
t_tm = localtime(&t);
|
|
} else
|
|
return NULL;
|
|
|
|
i = 0;
|
|
// if the file exists, it must be in any of the possible subdirs
|
|
// so try one after the next - one will match
|
|
while ( subdir_def[i] ) {
|
|
char const *sub_fmt = subdir_def[i];
|
|
char subpath[255];
|
|
struct stat stat_buf;
|
|
strftime(subpath, 254, sub_fmt, t_tm);
|
|
subpath[254] = '\0';
|
|
|
|
snprintf(s, MAXPATHLEN-1, "%s/%s/%s", channeldir, subpath, filename);
|
|
if ( stat(s, &stat_buf) == 0 && S_ISREG(stat_buf.st_mode) ) {
|
|
// found file in subdir
|
|
return strdup(subpath);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return NULL;
|
|
|
|
} // End of GuessSubDir
|
|
|
|
char *GetSubDir(struct tm *now ) {
|
|
static char subpath[255];
|
|
size_t sublen;
|
|
|
|
sublen = strftime(subpath, 254, subdir_format, now);
|
|
|
|
return sublen == 0 ? NULL : subpath;
|
|
|
|
} // End of GetSubDir
|
|
|
|
int SetupSubDir(char *dir, char *subdir, char *error, size_t errlen ) {
|
|
char *p, path[MAXPATHLEN];
|
|
struct stat stat_buf;
|
|
size_t sublen, pathlen;
|
|
int err;
|
|
|
|
error[0] = '\0';
|
|
|
|
path[0] = '\0';
|
|
strncat(path, dir, MAXPATHLEN-1);
|
|
path[MAXPATHLEN-1] = '\0';
|
|
|
|
sublen = strlen(subdir);
|
|
pathlen = strlen(path);
|
|
// set p as reference between path and subdir
|
|
if ( (sublen + pathlen + 2) >= (MAXPATHLEN-1) ) { // +2 : add 1 for '/'
|
|
snprintf(error, errlen, "Path '%s': too long", path);
|
|
return 0;
|
|
}
|
|
|
|
p = path + pathlen; // points to '\0' of path
|
|
*p++ = '/';
|
|
*p = '\0';
|
|
|
|
strncat(path, subdir, MAXPATHLEN-pathlen-2); // +2: add 1 for '/'
|
|
|
|
// our cwd is basedir ( -l ) so test if, dir exists
|
|
if ( stat(path, &stat_buf) == 0 ) {
|
|
if ( S_ISDIR(stat_buf.st_mode) ) {
|
|
// sub directory already exists
|
|
return 1;
|
|
} else {
|
|
// an entry with this name exists, but it's not a directory
|
|
snprintf(error, errlen, "Path '%s': %s ", path, strerror(ENOTDIR));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// no such entry exists - try to create the directory, assuming path below exists
|
|
err = mkdir(path, dir_mode);
|
|
if ( err == 0 ) // success
|
|
return 1;
|
|
|
|
// else errno is set
|
|
if ( errno == ENOENT ) { // we need to create intermediate directories as well
|
|
err = mkpath(path, p, mode, dir_mode, error, errlen);
|
|
if ( err == 0 ) // creation was successful
|
|
return 1;
|
|
} else {
|
|
snprintf(error, errlen, "mkdir() error for '%s': %s\n", path, strerror(errno));
|
|
}
|
|
|
|
// anything else failed and error string is set
|
|
return 0;
|
|
|
|
} // End of SetupSubDir
|
|
|
|
/*
|
|
* mkpath -- create directories.
|
|
* path - path
|
|
* p - separator path/subpath
|
|
* mode - file mode of terminal directory
|
|
* dir_mode - file mode of intermediate directories
|
|
*/
|
|
static int mkpath(char *path, char *p, mode_t mode, mode_t dir_mode, char *error, size_t errlen) {
|
|
struct stat sb;
|
|
char *slash;
|
|
int done = 0;
|
|
|
|
slash = p;
|
|
|
|
while (!done) {
|
|
slash += strspn(slash, "/");
|
|
slash += strcspn(slash, "/");
|
|
|
|
done = (*slash == '\0');
|
|
*slash = '\0';
|
|
|
|
if (stat(path, &sb)) {
|
|
if (errno != ENOENT || (mkdir(path, done ? mode : dir_mode) && errno != EEXIST)) {
|
|
snprintf(error, errlen, "mkdir() error for '%s': %s\n", path, strerror(errno));
|
|
return (-1);
|
|
}
|
|
} else if (!S_ISDIR(sb.st_mode)) {
|
|
snprintf(error, errlen, "Path '%s': %s ", path, strerror(ENOTDIR));
|
|
return (-1);
|
|
}
|
|
|
|
*slash = '/';
|
|
}
|
|
|
|
return (0);
|
|
|
|
} // End of mkpath
|
|
|