diff --git a/README.md b/README.md index 4892872..1f498d7 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,26 @@ This is an experiment to see how `os.Chmod` and `os.Stat` interact on Windows. T ## Usage -### `go-perm-test` - -Prints the results as a CSV in the format - -```,``` - -where each set of permissions is an octal integer. - -### `go-perm-test ` +``` +go-perm-test +``` Inserts the results into an SQLite database called `filename`. -There are two objects in the DB schema: a table `result_raw(expected INT, actual INT)` and a view `result`. +There are four objects in the DB schema: tables named `{file,dir}_result_raw(expected INT, actual INT)` and views named `{file,dir}_result`. -The `result` view breaks down the permissions into separate fields: The raw data are in the `expected` and `actual` columns, 3-bit fields are in `{expected,actual}_{u,g,o}` columns, and 1-bit fields are in `{expected,actual}_{u,g,o}{r,w,x}` columns. +The `result` views breaks down the permissions into separate fields: The raw data are in the `expected` and `actual` columns, 3-bit fields are in `{expected,actual}_{u,g,o}` columns, and 1-bit fields are in `{expected,actual}_{u,g,o}{r,w,x}` columns. ## Findings Permissions as observed through `os.Stat` depend entirely upon the owner's write bit set through `os.Chmod`: ``` -sqlite> select distinct actual, expected_uw from result; -actual|expected_uw -292|0 -438|1 +sqlite> select distinct format('%o', actual), expected_uw from dir_result; +20000000555|0 +20000000777|1 +sqlite> select distinct format('%o', actual), expected_uw from file_result; +444|0 +666|1 ``` This obviously isn't an exhaustive treatment of the subject, and I'm not really interested in doing one, but it corraborates [Michal Pristas’s findings.](https://archive.is/RZ8WP) \ No newline at end of file diff --git a/main_windows.go b/main_windows.go index 049b088..036510a 100644 --- a/main_windows.go +++ b/main_windows.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "io/fs" "os" @@ -36,56 +37,66 @@ func sqlViewColSpec() string { return strings.Join(cols, ",\n") } -const createTable = `CREATE TABLE result_raw ( +const createTableFile = `CREATE TABLE file_result_raw ( expected INT NOT NULL, actual INT NOT NULL );` -var createView string = `CREATE VIEW result AS +var createViewFile string = `CREATE VIEW file_result AS SELECT ` + sqlViewColSpec() + ` -FROM result_raw;` +FROM file_result_raw;` + +var createTableDir = strings.ReplaceAll(createTableFile, "file_result", "dir_result") +var createViewDir = strings.ReplaceAll(createViewFile, "file_result", "dir_result") func NewSQLite(path string) (*SQLiteEmitter, error) { conn, err := sqlite.OpenConn(path, sqlite.OpenCreate|sqlite.OpenReadWrite|sqlite.OpenWAL) if err != nil { return nil, err } - fmt.Println(createView) - err = sqlitex.Execute(conn, createTable, &sqlitex.ExecOptions{}) + script := strings.Join([]string{createTableFile, createTableDir, createViewFile, createViewDir}, "\n") + fmt.Println(script) + err = sqlitex.ExecuteScript(conn, script, &sqlitex.ExecOptions{}) if err != nil { - return nil, fmt.Errorf("NewSQLite: cannot create table: %w", err) - } - err = sqlitex.Execute(conn, createView, &sqlitex.ExecOptions{}) - if err != nil { - return nil, fmt.Errorf("NewSQLite: cannot create view: %w", err) + return nil, fmt.Errorf("NewSQLite: cannot create schema: %w", err) } + // err = sqlitex.Execute(conn, createTableFile, &sqlitex.ExecOptions{}) + // if err != nil { + // return nil, fmt.Errorf("NewSQLite: cannot create table: %w", err) + // } + // err = sqlitex.Execute(conn, createViewFile, &sqlitex.ExecOptions{}) + // if err != nil { + // return nil, fmt.Errorf("NewSQLite: cannot create view: %w", err) + // } return &SQLiteEmitter{ conn: conn, }, nil } -func (s *SQLiteEmitter) Emit(r Result) error { - return sqlitex.Execute(s.conn, "INSERT INTO result_raw(expected, actual) VALUES (?, ?)", &sqlitex.ExecOptions{ - Args: []any{r.Expected, r.Actual}, - }) -} - -func emitCSV(r Result) error { - _, err := fmt.Printf("%03o,%03o\n", r.Expected, r.Actual) - if err != nil { - return fmt.Errorf("emitCSV: %w", err) +func (s *SQLiteEmitter) Emit(tablePrefix string) func(r Result) error { + query := fmt.Sprintf("INSERT INTO %sresult_raw(expected, actual) VALUES (?, ?)", tablePrefix) + return func(r Result) error { + return sqlitex.Execute(s.conn, query, &sqlitex.ExecOptions{ + Args: []any{r.Expected, r.Actual}, + }) } - return nil } -func getData(emit func(Result) error) (err error) { - f, err := os.CreateTemp("", "go-perm-test-*") +func createFile(dir, pattern string) (path string, err error) { + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return "", err + } + f.Close() + return f.Name(), nil +} + +func getData(create func(string, string) (string, error), emit func(Result) error) (err error) { + path, err := create("", "go-perm-test-*") if err != nil { return err } - path := f.Name() - f.Close() for mode := fs.FileMode(0); mode <= os.ModePerm; mode++ { err := os.Chmod(path, mode) @@ -109,19 +120,18 @@ func getData(emit func(Result) error) (err error) { } func run() error { - var emit func(Result) error if len(os.Args) < 2 { - emit = emitCSV - } else { - dbPath := os.Args[1] - db, err := NewSQLite(dbPath) - if err != nil { - return err - } - defer db.Close() - emit = db.Emit + fmt.Fprintf(os.Stderr, "usage: %s ", os.Args[0]) } - return getData(emit) + + dbPath := os.Args[1] + db, err := NewSQLite(dbPath) + if err != nil { + return err + } + defer db.Close() + + return errors.Join(getData(createFile, db.Emit("file_")), getData(os.MkdirTemp, db.Emit("dir_"))) } func main() { diff --git a/modes.db b/modes.db index 9ee6054..a7b7e69 100644 Binary files a/modes.db and b/modes.db differ