Billionmail/BillionMail · error

failed to read file %s: %v

Error message

failed to read file %s: %v

What it means

createBackups reads PostfixMainCfPath and PostfixMasterCfPath to snapshot them before config changes; this error wraps ioutil.ReadFile failure for one of those files and aborts the backup (and thus the whole ApplyConfigsWithRollback run, via the 'failed to create backup' wrapper).

Source

Thrown at core/internal/service/multi_ip_domain/config_manager.go:89

		}
		return fmt.Errorf("failed to apply configurations, but rollback succeeded: %v", err)
	}

	m.cleanupBackups(ctx, backups)
	return nil
}

// createBackups Create backups for Postfix's main.cf and master.cf
func (m *ConfigManager) createBackups(ctx context.Context) (map[string]string, error) {
	backups := make(map[string]string)

	files := []string{PostfixMainCfPath, PostfixMasterCfPath}

	for _, file := range files {
		if gfile.Exists(file) {
			content, err := ioutil.ReadFile(file)
			if err != nil {
				return nil, fmt.Errorf("failed to read file %s: %v", file, err)
			}

			backupPath := fmt.Sprintf("%s.backup.%d", file, time.Now().UnixNano())
			if err := ioutil.WriteFile(backupPath, content, 0644); err != nil {
				return nil, fmt.Errorf("failed to create backup for %s: %v", file, err)
			}
			g.Log().Debugf(ctx, "Created backup for %s at %s", file, backupPath)
			backups[file] = backupPath
		}
	}

	return backups, nil
}

// rollback Roll back configurations
func (m *ConfigManager) rollback(ctx context.Context, backups map[string]string) error {
	var allErrors []string
	for file, backup := range backups {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Verify the file exists at the configured path constant and the process can read it (ls -l, check uid of the service)
  2. Fix permissions or run the service with access to /etc/postfix
  3. Re-check path constants PostfixMainCfPath/PostfixMasterCfPath match the actual deployment layout
  4. Handle ENOENT gracefully — if the file is optional, skip backup for it instead of failing the whole apply

Example fix

// before
content, err := ioutil.ReadFile(file)
if err != nil {
    return nil, fmt.Errorf("failed to read file %s: %v", file, err)
}
// after
content, err := ioutil.ReadFile(file)
if err != nil {
    if os.IsNotExist(err) {
        continue // nothing to back up for this file
    }
    return nil, fmt.Errorf("failed to read file %s: %v", file, err)
}
Defensive patterns

Strategy: validation

Validate before calling

func backupable(path string) error {
    fi, err := os.Stat(path)
    if err != nil { return err }
    if !fi.Mode().IsRegular() { return fmt.Errorf("%s is not a regular file", path) }
    f, err := os.Open(path)
    if err != nil { return err }
    return f.Close()
}
// run for both PostfixMainCfPath and PostfixMasterCfPath before applying

Try / catch

err := manager.ApplyConfigsWithRollback(ctx, configs)
if err != nil {
    var pathErr *fs.PathError
    if errors.As(err, &pathErr) && (errors.Is(pathErr.Err, fs.ErrPermission) || errors.Is(pathErr.Err, fs.ErrNotExist)) {
        // fix deployment/permissions before retrying
        reportMisconfiguration(pathErr)
    }
}

Prevention

When it happens

Trigger: ioutil.ReadFile(file) fails on an existing file listed in files — typically EACCES (process lacks read permission), ENOENT via race (file deleted between gfile.Exists and ReadFile), or EISDIR/IO error.

Common situations: Running in a container without postfix config mounted or with restricted permissions; path constants pointing to the wrong location for the deployment; file removed concurrently by another tool; SELinux denials.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/44b6ed22557d105e. Report an issue: GitHub.