golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/conf/migration_windows.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package conf
     7  
     8  import (
     9  	"errors"
    10  	"io"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"golang.org/x/sys/windows"
    19  )
    20  
    21  var (
    22  	migrating          sync.Mutex
    23  	lastMigrationTimer *time.Timer
    24  )
    25  
    26  type MigrationCallback func(name, oldPath, newPath string)
    27  
    28  func MigrateUnencryptedConfigs(migrated MigrationCallback) { migrateUnencryptedConfigs(3, migrated) }
    29  
    30  func migrateUnencryptedConfigs(sharingBase int, migrated MigrationCallback) {
    31  	if migrated == nil {
    32  		migrated = func(_, _, _ string) {}
    33  	}
    34  	migrating.Lock()
    35  	defer migrating.Unlock()
    36  	configFileDir, err := tunnelConfigurationsDirectory()
    37  	if err != nil {
    38  		return
    39  	}
    40  	files, err := os.ReadDir(configFileDir)
    41  	if err != nil {
    42  		return
    43  	}
    44  	ignoreSharingViolations := false
    45  	for _, file := range files {
    46  		path := filepath.Join(configFileDir, file.Name())
    47  		name := filepath.Base(file.Name())
    48  		if len(name) <= len(configFileUnencryptedSuffix) || !strings.HasSuffix(name, configFileUnencryptedSuffix) {
    49  			continue
    50  		}
    51  		if !file.Type().IsRegular() {
    52  			continue
    53  		}
    54  		info, err := file.Info()
    55  		if err != nil {
    56  			continue
    57  		}
    58  		if info.Mode().Perm()&0o444 == 0 {
    59  			continue
    60  		}
    61  
    62  		var bytes []byte
    63  		var config *Config
    64  		var newPath string
    65  		// We don't use os.ReadFile, because we actually want RDWR, so that we can take advantage
    66  		// of Windows file locking for ensuring the file is finished being written.
    67  		f, err := os.OpenFile(path, os.O_RDWR, 0)
    68  		if err != nil {
    69  			if errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
    70  				if ignoreSharingViolations {
    71  					continue
    72  				} else if sharingBase > 0 {
    73  					if lastMigrationTimer != nil {
    74  						lastMigrationTimer.Stop()
    75  					}
    76  					lastMigrationTimer = time.AfterFunc(time.Second/time.Duration(sharingBase*sharingBase), func() { migrateUnencryptedConfigs(sharingBase-1, migrated) })
    77  					ignoreSharingViolations = true
    78  					continue
    79  				}
    80  			}
    81  			goto error
    82  		}
    83  		bytes, err = io.ReadAll(f)
    84  		f.Close()
    85  		if err != nil {
    86  			goto error
    87  		}
    88  		config, err = FromWgQuickWithUnknownEncoding(string(bytes), strings.TrimSuffix(name, configFileUnencryptedSuffix))
    89  		if err != nil {
    90  			goto error
    91  		}
    92  		err = config.Save(false)
    93  		if err != nil {
    94  			goto error
    95  		}
    96  		err = os.Remove(path)
    97  		if err != nil {
    98  			goto error
    99  		}
   100  		newPath, err = config.Path()
   101  		if err != nil {
   102  			goto error
   103  		}
   104  		migrated(config.Name, path, newPath)
   105  		continue
   106  	error:
   107  		log.Printf("Unable to ingest and encrypt %#q: %v", path, err)
   108  	}
   109  }