/*!@file \brief APC Directory Scanner \details This hand-written parser/scanner traverses a directory tree and tokenizes elements of the structure which correspond to APC grammar. The parser is implemented as a 2D stack which populates a list of child directories at each depth, handling only the leaf nodes (regular files) of the directory open at the current depth to conserve memory and speed up traversal. The scanner works with the lexer to lexically analyze text, and assumes the existence of an external 'lex' function \author Jordan Lavatai \date Aug 2016 ----------------------------------------------------------------------------*/ /* Standard */ #include //print #include //strncmp #include //errno #include //tolower /* Posix */ #include //warnx #include //exit #include //chdir #include //opendir #include //unicode strings /* Internal */ #include "parser.tab.h" /* Public */ int scanner_init(void); void scanner_quit(void); int scanner(void); int scanner_scanpixels(int*,int); /* Private */ extern //lexer.c int lexer_lexstring(const uint8_t*); extern //lexer.c void lexer_pushtok(int, int); static int dredge_current_depth(void); /* Mem */ extern //lexer.c struct dirent* lexer_direntpa[], **lexer_direntpp; extern //SRC_DIR/bin/tools/apc.c const char* cargs['Z']; #ifndef DL_STACKSIZE #define DL_STACKSIZE 64 #endif #ifndef DL_CD_STACKSIZE #define DL_CD_STACKSIZE DL_STACKSIZE //square tree #endif static struct dirlist { DIR* dirp; struct dirent* child_directory_stack[DL_CD_STACKSIZE],** cds; } directory_list_stack[DL_STACKSIZE + 1],* dls; //+1 for the root dir static FILE* current_open_file = NULL; /* Directory Listing Stack FILO Stack for keeping an open DIR* at each directory depth for treewalk. This stack is depth-safe, checking its depth during push operations, but not during pop operations, to ensure the thread doesn't open too many files at once (512 in c runtime), or traverse too far through symbolic links. A directory listing includes a DIR* and all DIR-typed entity in the directory as recognized by dirent, populated externally (and optionally). This stack behaves abnormally by incrementing its PUSH operation prior to evaluation, and the POP operations after evaluation. This behavior allows the 'DL_CURDEPTH' operation to map to the current element in the 'dl_stack' array, and it is always treated as the "current depth". This also allows us to init the root directory to 'directory_list_stack'[0] and pop it in a safe and explicit manner. */ #define DL_STACK (directory_list_stack) #define DL_STACKP (dls) #define DL_CD_STACK ((*DL_STACKP).child_directory_stack) #define DL_CD_STACKP ((*DL_STACKP).cds) #define DL_CURDIR() ((*DL_STACKP).dirp) #define DL_LEN() (DL_STACKP - DL_STACK) #define DL_CD_LEN() (DL_CD_STACKP - DL_CD_STACK) #define DL_INIT() (DL_STACKP = DL_STACK) #define DL_CD_INIT() (DL_CD_STACKP = DL_CD_STACK) #define DL_POP() ((*DL_STACKP--).dirp) #define DL_CD() (*DL_CD_STACKP) #define DL_CD_CURNAME() (DL_CD()->d_name) #define DL_CD_POP() (*--DL_CD_STACKP) #define DL_PUSH(D) ((*++DL_STACKP).dirp = D) #define DL_CD_PUSH(E) (*DL_CD_STACKP++ = E) /* Initializer Initializer expects a function pointer to its lexical analysis function. Sets up stack pointers and returns boolean true if 'opendir' encounters an error, or if dredge_current_depth returns boolean true. */ int scanner_init #define CWDSTR "./" #define ROOTDIR (cargs['d'] ? cargs['d'] : CWDSTR) () { DL_INIT(); DL_STACK[0].dirp = opendir(ROOTDIR); if (current_open_file != NULL) { fclose(current_open_file); current_open_file = NULL; } printf("Root dir %s\n",ROOTDIR); return !chdir(ROOTDIR) && (DL_STACK[0].dirp == NULL || dredge_current_depth() == -1); } /* Quit */ void scanner_quit () { if (DL_CURDIR()) closedir(DL_CURDIR()); } /* Scanner The main driver of the scanner will advance the current treewalk state and tokenize tree-based push/pop operations. It will call 'lexer_lex' to tokenize directory names prior to making a push operation. safe checking for all returns from the filesystem handler will exit on serious system errors. after pushing a new directory to the directory list, the scanner will dredge the directory and alphabetically sort all file entries into the lexer's file array, while placing all subdirectory entries in the current depth's child directory stack to be scanned later. Returns the number of tokens generated on success, -1 on error. */ int scanner #define $($)#$ //stringifier #define ERR_CHILD "Fatal: Maximum of " $(DL_CD_STACKSIZE) \ " child directories exceeded for directory at depth %i\n" \ ,DL_LEN() #define ERR_DEPTH "Fatal: Maximum directory depth of " $(DL_STACKSIZE) \ " exceeded during directory scan\n" #define ERR_DL "Fatal: Directory List Stack Corruption %x\n", DL_LEN() () { int ntok = 0; scan: if (DL_CD_LEN() >= DL_CD_STACKSIZE)//fail if maxchildren exceeded { fprintf(stderr, ERR_CHILD); goto fail; } if (DL_CD_LEN() > 0) //There are entities to process { if (DL_CD_POP() == NULL) //If the dirent is null, then the goto libfail; //lib function in dirent has failed <<<<<<< HEAD ntok += lexer_lexstring(DL_CD_CURNAME());//lex the directory name ======= ntok += lexer_lexstring(DL_CD_CURNAME()); //lex the directory name >>>>>>> 15d3ab5e7871ff459af13089b82bf5f17f731ebd if (DL_LEN() >= DL_STACKSIZE) //fail if maxdepth exceeded { fprintf(stderr, ERR_DEPTH); goto fail; } if (chdir(DL_CD_CURNAME())) //move into the new directory goto libfail; if (DL_CURDIR() == NULL) //open the cwd goto libfail; lexer_pushtok(CLOPEN, 0); //Push "Open Directory" token ntok++; return dredge_current_depth(); //Filter and sort the current depth } else if (DL_LEN() >= 0) //Any dirs left? (Including root) { if (closedir(DL_POP())) //close the directory we just left goto libfail; if (DL_LEN() == -1) //If we just popped root, goto done; //we're done lexer_pushtok(CLCLOSE, 0); //Else push "Close Directory" token, ntok++; if (!chdir("..")) //move up a directory and goto scan; //start over } fprintf(stderr, ERR_DL); libfail: perror("scanner: "); fail: return -1; done: return ntok; } /* Scan Pixels Scans up to 'len' pixels from the current file into 'buf'. Returns the number of pixels scanned from the file, or -1 on error */ int scanner_scanpixels ( int* buf, int max_len ) { static int col_len, row_len, row; //Open the current file if not yet open if (current_open_file == NULL) { if ((current_open_file = fopen(DL_CD_CURNAME(),"rb")) == NULL) { perror("fopen: "); return -1; } //Verify file header, get row_len/col_len //if (read_img_header(&row_len, &col_len)) //return -1; row = 0; } //Read pixels into the buffer if there are rows left in the image if (row++ < row_len) //TODO: return read_img_pixels(buf, col_len); printf("SCANPIXELS NOT IMPLEMENTED\n."); //Close the file and return 0 fclose(current_open_file); current_open_file = NULL; return 0; } /* Directory Entity Sort and Filter (Dredge) This filter removes all unhandled file types, and places any 'DT_DIR' type files in the current Directory List's directory stack. Upon finishing, the 'CE_STACK' is sorted alphabetically, and the current 'DL_CD_STACK' is populated. Prints warnings for unhandled files. Returns -1 if 'readdir' encounters an error, otherwise returns the number of directory entries sent to the external 'lexer_direntpa' array. */ typedef //so we can typecast dirent's 'alphasort()' to take const void*s int (*qcomp)(const void*, const void*); static inline int dredge_current_depth #define READDIR_ERROR (-1) #define READDIR_DONE (0) #define DPS_LEN() (lexer_direntpp - lexer_direntpa) #define DPS_PUSH(E) (*lexer_direntpp++ = E) () { struct dirent** direntpp = lexer_direntpa; DIR* cwd = DL_CURDIR(); struct dirent* direntp; DL_CD_INIT(); scan_next: if ((direntp = readdir(cwd)) != NULL) { switch (direntp->d_type) { case DT_REG: DPS_PUSH(direntp); goto scan_next; case DT_DIR: if (*(direntp->d_name) == '.') //skip hidden files and relative dirs goto scan_next; DL_CD_PUSH(direntp); goto scan_next; case DT_UNKNOWN: warnx("unknown file %s: ignoring", direntp->d_name); default: goto scan_next; } } if (errno) return -1; qsort(lexer_direntpa, DPS_LEN(), sizeof direntp, (qcomp)alphasort); return DPS_LEN(); }