Skip to content

Commit

Permalink
Use the write-and-rename paradigm for all JSON files.
Browse files Browse the repository at this point in the history
This ensures that clients don't pick up partial writes for any JSON
files, including logs, manifests, summaries and so on.
  • Loading branch information
LTLA committed Feb 20, 2024
1 parent ca22488 commit 1920359
Showing 1 changed file with 38 additions and 44 deletions.
82 changes: 38 additions & 44 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,45 @@ func newGlobalConfiguration(registry string) globalConfiguration {
}
}

func dumpJson(path string, output interface{}) error {
str, err := json.MarshalIndent(output, "", " ")
func dumpJson(path string, content interface{}) error {
// Using the save-and-rename paradigm to avoid clients picking up partial writes.
temp, err := os.CreateTemp(filepath.Dir(path), ".temp*.json")
if err != nil {
return fmt.Errorf("failed to create temporary file when saving %q; %w", path, err)
}

is_closed := false
defer func() {
if !is_closed {
temp.Close()
}
}()

err = os.Chmod(temp.Name(), 0644)
if err != nil {
return fmt.Errorf("failed to marshal JSON; %w", err)
return fmt.Errorf("failed to set temporary file permissions when saving %q; %w", path, err);
}

err = os.WriteFile(path, str, 0644)
as_str, err := json.MarshalIndent(content, "", " ")
if err != nil {
return fmt.Errorf("failed to write to %q; %w", path, err)
return fmt.Errorf("failed to marshal JSON to save to %q; %w", path, err)
}

_, err = temp.Write(as_str)
if err != nil {
return fmt.Errorf("failed to write JSON to temporary file for %q; %w", path, err)
}

temp_name := temp.Name()
is_closed = true
err = temp.Close()
if err != nil {
return fmt.Errorf("failed to close temporary file when saving to %q; %w", path, err)
}

err = os.Rename(temp_name, path)
if err != nil {
return fmt.Errorf("failed to rename temporary file to %q; %w", path, err)
}

return nil
Expand Down Expand Up @@ -63,47 +93,11 @@ func isMissingOrBadName(name *string) error {

const logDirName = "..logs"

func dumpLog(registry string, output interface{}) error {
func dumpLog(registry string, content interface{}) error {
path := time.Now().Format(time.RFC3339) + "_" + strconv.Itoa(100000 + rand.Intn(900000))
return dumpJson(filepath.Join(registry, logDirName, path), output)
return dumpJson(filepath.Join(registry, logDirName, path), content)
}

func dumpResponse(response_dir, reqname string, content interface{}) error {
// Using the save-and-rename paradigm to avoid clients picking up partial writes.
temp, err := os.CreateTemp(response_dir, "TEMP")
if err != nil {
return fmt.Errorf("failed to create temporary file for response to %q; %w", reqname, err)
}

is_closed := false
defer func() {
if !is_closed {
temp.Close()
}
}()

as_str, err := json.MarshalIndent(content, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON for response to %q; %w", reqname, err)
}

_, err = temp.Write(as_str)
if err != nil {
return fmt.Errorf("failed to write JSON for response to %q; %w", reqname, err)
}

temp_name := temp.Name()
is_closed = true
err = temp.Close()
if err != nil {
return fmt.Errorf("failed to close file for response to %q; %w", reqname, err)
}

logpath := filepath.Join(response_dir, reqname)
err = os.Rename(temp_name, logpath)
if err != nil {
return fmt.Errorf("failed to rename response to %q; %w", reqname, err)
}

return nil
return dumpJson(filepath.Join(response_dir, reqname), content)
}

0 comments on commit 1920359

Please sign in to comment.