#! /usr/bin/env tclsh
set obsfucate 0
if {[lindex $argv end] == "--obsfucate"} {
set obsfucate 1
set argv [lrange $argv 0 end-1]
}
if {[llength $argv] != 2} {
puts stderr "Usage: dir2c.tcl <hashkey> <startdir> \[--obsfucate\]"
exit 1
}
set hashkey [lindex $argv 0]
set startdir [lindex $argv 1]
proc shorten_file {dir file} {
set dirNameLen [string length $dir]
if {[string range $file 0 [expr {$dirNameLen - 1}]] == $dir} {
set file [string range $file $dirNameLen end]
}
if {[string index $file 0] == "/"} {
set file [string range $file 1 end]
}
return $file
}
proc recursive_glob {dir} {
set children [glob -nocomplain -directory $dir *]
set ret [list $dir]
foreach child $children {
unset -nocomplain childinfo
catch {
file stat $child childinfo
}
if {![info exists childinfo(type)]} {
continue
}
if {$childinfo(type) == "directory"} {
foreach add [recursive_glob $child] {
lappend ret $add
}
continue
}
if {$childinfo(type) != "file"} {
continue
}
lappend ret $child
}
return $ret
}
# Convert a string into a C-style binary string
## XXX: This function needs to be optimized
proc stringify {data} {
set ret "\""
for {set idx 0} {$idx < [string length $data]} {incr idx} {
binary scan [string index $data $idx] H* char
append ret "\\x${char}"
if {($idx % 20) == 0 && $idx != 0} {
append ret "\"\n\""
}
}
set ret [string trim $ret "\n\""]
set ret "\"$ret\""
return $ret
}
# Encrypt the data
proc random_byte {} {
set value [expr {int(256 * rand())}]
return $value
}
proc encrypt_key_generate {method} {
switch -- $method {
"rotate_subst" {
set key(method) $method
set key(rotate_length) [random_byte]
set key_data [list]
while {[llength $key_data] != 256} {
set value [random_byte]
if {[lsearch -exact $key_data $value] != -1} {
continue
}
lappend key_data $value
}
foreach value $key_data {
append key(subst) [format %c $value]
}
return [array get key]
}
}
error "not implemented"
}
proc encrypt_key_export {key_dict target} {
array set key $key_dict
switch -- $key(method) {
"rotate_subst" {
switch -- $target {
"c" {
set retval ".type = CVFS_KEYTYPE_ROTATE_SUBST,\n"
append retval ".typedata.rotate_subst.rotate_length = $key(rotate_length),\n"
append retval ".typedata.rotate_subst.subst = [stringify $key(subst)]\n"
return $retval
}
}
}
}
error "not implemented"
}
proc encrypt {data key_dict {decrypt 0}} {
array set key $key_dict
switch -- $key(method) {
"rotate_subst" {
set retval ""
set datalen [string length $data]
for {set i 0} {$i < $datalen} {incr i $key(rotate_length)} {
set map [list]
for {set j 0} {$j < 256} {incr j} {
if {$decrypt} {
lappend map [format %c $j] [string index $key(subst) $j]
} else {
lappend map [string index $key(subst) $j] [format %c $j]
}
}
set end [expr {$i + $key(rotate_length) - 1}]
set block [string range $data $i $end]
set block [string map $map $block]
append retval $block
set key_end [string index $key(subst) 0]
set key(subst) [string range $key(subst) 1 end]$key_end
}
return $retval
}
"aes" {
package require aes
}
}
error "not implemented"
}
proc decrypt {data key_dict} {
return [encrypt $data $key_dict 1]
}
# This function must be kept in-sync with the generated C function below
proc cvfs_hash {path} {
set h 0
set g 0
for {set idx 0} {$idx < [string length $path]} {incr idx} {
binary scan [string index $path $idx] H* char
set char "0x$char"
set h [expr {($h << 4) + $char}]
set g [expr {$h & 0xf0000000}]
if {$g != 0} {
set h [expr {($h & 0xffffffff) ^ ($g >> 24)}]
}
set h [expr {$h & ((~$g) & 0xffffffff)}]
}
return $h
}
# If encryption is requested, generate a key
if {$obsfucate} {
set obsfucation_key [encrypt_key_generate "rotate_subst"]
}
# Generate list of files to include in output
set files [recursive_glob $startdir]
# Insert dummy entry cooresponding to C dummy entry
set files [linsert $files 0 "__DUMMY__"]
# Produce C89 compatible header
set cpp_tag "CVFS_[string toupper $hashkey]"
set code_tag "cvfs_[string tolower $hashkey]"
set hashkey [string tolower $hashkey]
puts "#ifndef $cpp_tag"
puts "# define $cpp_tag 1"
puts {
# ifdef HAVE_STDC
# ifndef HAVE_UNISTD_H
# define HAVE_UNISTD_H 1
# endif
# ifndef HAVE_STRING_H
# define HAVE_STRING_H 1
# endif
# ifndef HAVE_STDLIB_H
# define HAVE_STDLIB_H 1
# endif
# endif
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
# ifdef HAVE_STRING_H
# include <string.h>
# endif
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
# ifndef LOADED_CVFS_COMMON
# define LOADED_CVFS_COMMON 1
typedef enum {
CVFS_FILETYPE_FILE = 0,
CVFS_FILETYPE_DIR = 1,
CVFS_FILETYPE_OBSFUCATED_FILE = 2,
CVFS_FILETYPE_ENCRYPTED_FILE = 3,
} cvfs_filetype_t;
struct cvfs_data {
const char * name;
unsigned long index;
unsigned long size;
cvfs_filetype_t type;
const unsigned char * data;
};
typedef enum {
CVFS_KEYTYPE_ROTATE_SUBST = 0,
} cvfs_keytype_t;
struct cvfs_key {
cvfs_keytype_t type;
union {
struct {
int rotate_length;
unsigned char *subst;
} rotate_subst;
} typedata;
};
static unsigned long cvfs_hash(const unsigned char *path) {
unsigned long i, h = 0, g = 0;
for (i = 0; path[i]; i++) {
h = (h << 4) + path[i];
g = h & 0xf0000000;
if (g) {
h ^= (g >> 24);
}
h &= ((~g) & 0xffffffffLU);
}
return(h);
}
#include <stdio.h>
static int cvfs_decrypt(unsigned char *out, const unsigned char *in, unsigned long in_out_length, struct cvfs_key *key) {
unsigned long i;
unsigned char in_ch, out_ch;
int ch_idx;
for (i = 0; i < in_out_length; i++) {
in_ch = in[i];
ch_idx = (in_ch + (i / key->typedata.rotate_subst.rotate_length)) % 256;
out_ch = key->typedata.rotate_subst.subst[ch_idx];
out[i] = out_ch;
}
return(0);
}
# endif /* !LOADED_CVFS_COMMON */}
puts ""
puts "static struct cvfs_data ${code_tag}_data\[\] = {"
puts "\t{"
puts "\t\t.name = NULL,"
puts "\t\t.index = 0,"
puts "\t\t.type = 0,"
puts "\t\t.size = 0,"
puts "\t\t.data = NULL,"
puts "\t},"
for {set idx 1} {$idx < [llength $files]} {incr idx} {
set file [lindex $files $idx]
set shortfile [shorten_file $startdir $file]
unset -nocomplain finfo type
file stat $file finfo
switch -- $finfo(type) {
"file" {
set size $finfo(size)
set fd [open $file]
fconfigure $fd -translation binary
set data [read $fd]
close $fd
if {$obsfucate} {
set type "CVFS_FILETYPE_OBSFUCATED_FILE"
set data "(unsigned char *) [stringify [encrypt $data $obsfucation_key]]"
} else {
set type "CVFS_FILETYPE_FILE"
set data "(unsigned char *) [stringify $data]"
}
}
"directory" {
set type "CVFS_FILETYPE_DIR"
set data "NULL"
set size 0
}
}
puts "\t{"
puts "\t\t.name = \"$shortfile\","
puts "\t\t.index = $idx,"
puts "\t\t.type = $type,"
puts "\t\t.size = $size,"
puts "\t\t.data = $data,"
puts "\t},"
}
puts "};"
puts ""
puts "static unsigned long ${code_tag}_lookup_index(const char *path) {"
puts "\tswitch (cvfs_hash((unsigned char *) path)) {"
for {set idx 1} {$idx < [llength $files]} {incr idx} {
set file [lindex $files $idx]
set shortfile [shorten_file $startdir $file]
set hash [cvfs_hash $shortfile]
lappend indexes_per_hash($hash) [list $shortfile $idx]
}
foreach {hash idx_list} [array get indexes_per_hash] {
puts "\t\tcase $hash:"
if {[llength $idx_list] == 1} {
set idx [lindex $idx_list 0 1]
puts "\t\t\treturn($idx);"
} else {
foreach idx_ent $idx_list {
set shortfile [lindex $idx_ent 0]
set idx [lindex $idx_ent 1]
puts "\t\t\tif (strcmp(path, \"$shortfile\") == 0) return($idx);"
}
puts "\t\t\tbreak;"
}
}
puts "\t}"
puts "\treturn(0);"
puts "}"
puts ""
puts "static struct cvfs_data *${code_tag}_getData(const char *path, unsigned long index) {"
puts "\tif (path != NULL) {"
puts "\t\tindex = ${code_tag}_lookup_index(path);"
puts "\t}"
puts ""
puts "\tif (index == 0) {"
puts "\t\treturn(NULL);"
puts "\t}"
puts ""
puts "\tif (path != NULL) {"
puts "\t\tif (strcmp(path, ${code_tag}_data\[index\].name) != 0) {"
puts "\t\t\treturn(NULL);"
puts "\t\t}"
puts "\t}"
puts ""
puts "\treturn(&${code_tag}_data\[index\]);"
puts "}"
puts ""
puts "static unsigned long ${code_tag}_getChildren(const char *path, unsigned long *outbuf, unsigned long outbuf_count) {"
puts "\tunsigned long index;"
puts "\tunsigned long num_children = 0;"
puts ""
puts "\tindex = ${code_tag}_lookup_index(path);"
puts "\tif (index == 0) {"
puts "\t\treturn(0);"
puts "\t}"
puts ""
puts "\tif (${code_tag}_data\[index\].type != CVFS_FILETYPE_DIR) {"
puts "\t\treturn(0);"
puts "\t}"
puts ""
puts "\tif (strcmp(path, ${code_tag}_data\[index\].name) != 0) {"
puts "\t\treturn(0);"
puts "\t}"
puts ""
puts "\tswitch (index) {"
unset -nocomplain children
array set children [list]
for {set idx 1} {$idx < [llength $files]} {incr idx} {
set file [lindex $files $idx]
set shortfile [shorten_file $startdir $file]
unset -nocomplain finfo type
file stat $file finfo
if {$finfo(type) != "directory"} {
continue;
}
# Determine which children are under this directory
## Convert the current pathname to a regular expression that matches exactly
set file_regexp [string map [list "\\" "\\\\" "." "\\." "\{" "\\\{" "\}" "\\\}" "*" "\\*"] $file]
## Search for pathnames which start with exactly our name, followed by a slash
## followed by no more slashes (direct descendants)
set child_idx_list [lsearch -regexp -all $files "^${file_regexp}/\[^/\]*$"]
set children($idx) $child_idx_list
puts "\t\tcase $idx:"
puts "\t\t\tnum_children = [llength $child_idx_list];"
puts "\t\t\tbreak;"
}
puts "\t}"
puts ""
puts "\tif (outbuf == NULL) {"
puts "\t\treturn(num_children);"
puts "\t}"
puts ""
puts "\tif (num_children > outbuf_count) {"
puts "\t\tnum_children = outbuf_count;"
puts "\t}"
puts ""
puts "\tif (num_children == 0) {"
puts "\t\treturn(num_children);"
puts "\t}"
puts ""
puts "\tif (outbuf_count > num_children) {"
puts "\t\toutbuf_count = num_children;"
puts "\t}"
puts ""
puts "\tswitch (index) {"
foreach {idx child_idx_list} [array get children] {
if {[llength $child_idx_list] == 0} {
continue
}
puts "\t\tcase $idx:"
puts "\t\t\tswitch(outbuf_count) {"
for {set child_idx_idx [expr {[llength $child_idx_list] - 1}]} {$child_idx_idx >= 0} {incr child_idx_idx -1} {
set child_idx [lindex $child_idx_list $child_idx_idx]
puts "\t\t\t\tcase [expr {$child_idx_idx + 1}]:"
puts "\t\t\t\t\toutbuf\[$child_idx_idx\] = $child_idx;"
}
puts "\t\t\t}"
puts "\t\t\tbreak;"
}
puts "\t}"
puts ""
puts "\treturn(num_children);"
puts "}"
puts ""
if {$obsfucate} {
puts "static void ${code_tag}_decryptFile(const char *path, struct cvfs_data *finfo) {"
puts "\tstatic struct cvfs_key key = { [string map [list "\n" " "] [encrypt_key_export $obsfucation_key "c"]] };"
puts "\tunsigned char *new_data;"
puts "\tint decrypt_ret;"
puts ""
puts "\tnew_data = malloc(finfo->size);"
puts "\tdecrypt_ret = cvfs_decrypt(new_data, finfo->data, finfo->size, &key);"
puts "\tif (decrypt_ret != 0) {"
puts "\t\tfree(new_data);"
puts ""
puts "\t\treturn;"
puts "\t}"
puts "\tfinfo->data = new_data;"
puts "\tfinfo->type = CVFS_FILETYPE_FILE;"
puts "\treturn;"
puts "}"
puts ""
}
puts "# ifdef CVFS_MAKE_LOADABLE"
set fd [open "cvfs_data.c"]
puts [read $fd]
close $fd
puts "static cmd_getData_t *getCmdData(const char *hashkey) {"
puts "\treturn(${code_tag}_getData);"
puts "}"
puts ""
puts "static cmd_getChildren_t *getCmdChildren(const char *hashkey) {"
puts "\treturn(${code_tag}_getChildren);"
puts "}"
puts ""
puts "static cmd_decryptFile_t *getCmdDecryptFile(const char *hashkey) {"
if {$obsfucate} {
puts "\treturn(${code_tag}_decryptFile);"
} else {
puts "\treturn(NULL);"
}
puts "}"
puts ""
puts "int Cvfs_data_${hashkey}_Init(Tcl_Interp *interp) {"
puts "\tTcl_Command tclCreatComm_ret;"
puts "\tint tclPkgProv_ret;"
puts ""
puts "\ttclCreatComm_ret = Tcl_CreateObjCommand(interp, \"::vfs::cvfs::data::${hashkey}::getMetadata\", getMetadata, NULL, NULL);"
puts "\tif (!tclCreatComm_ret) {"
puts "\t\treturn(TCL_ERROR);"
puts "\t}"
puts ""
puts "\ttclCreatComm_ret = Tcl_CreateObjCommand(interp, \"::vfs::cvfs::data::${hashkey}::getData\", getData, NULL, NULL);"
puts "\tif (!tclCreatComm_ret) {"
puts "\t\treturn(TCL_ERROR);"
puts "\t}"
puts ""
puts "\ttclCreatComm_ret = Tcl_CreateObjCommand(interp, \"::vfs::cvfs::data::${hashkey}::getChildren\", getChildren, NULL, NULL);"
puts "\tif (!tclCreatComm_ret) {"
puts "\t\treturn(TCL_ERROR);"
puts "\t}"
puts ""
puts "\ttclPkgProv_ret = Tcl_PkgProvide(interp, \"vfs::cvfs::data::${hashkey}\", \"1.0\");"
puts ""
puts "\treturn(tclPkgProv_ret);"
puts "\t}"
puts "# endif /* CVFS_MAKE_LOADABLE */"
puts "#endif /* !$cpp_tag */"