github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/pfs-crash/main.go (about)

     1  // Copyright (c) 2015-2021, NVIDIA CORPORATION.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package main
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"container/list"
    10  	cryptoRand "crypto/rand"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"log"
    15  	"math/big"
    16  	mathRand "math/rand"
    17  	"net"
    18  	"net/http"
    19  	"os"
    20  	"os/exec"
    21  	"os/signal"
    22  	"path/filepath"
    23  	"strconv"
    24  	"strings"
    25  	"time"
    26  
    27  	"golang.org/x/sys/unix"
    28  
    29  	"github.com/swiftstack/ProxyFS/conf"
    30  	"github.com/swiftstack/ProxyFS/httpserver"
    31  	"github.com/swiftstack/ProxyFS/transitions"
    32  )
    33  
    34  const (
    35  	proxyfsdHalterMinHaltAfterCount = uint64(400)
    36  	proxyfsdHalterMaxHaltAfterCount = uint64(800)
    37  
    38  	proxyfsdMinKillDelay = 10 * time.Second
    39  	proxyfsdMaxKillDelay = 20 * time.Second
    40  
    41  	proxyfsdPollDelay = 100 * time.Millisecond
    42  
    43  	openDirPollDelay = 100 * time.Millisecond
    44  
    45  	pseudoRandom     = false
    46  	pseudoRandomSeed = int64(0)
    47  )
    48  
    49  type queryMethodType uint16
    50  
    51  const (
    52  	queryMethodGET queryMethodType = iota
    53  	queryMethodPOST
    54  )
    55  
    56  var (
    57  	confFile              string
    58  	fuseMountPointName    string
    59  	haltLabelStrings      []string
    60  	includeHalterTriggers bool
    61  	ipAddrTCPPort         string
    62  	mathRandSource        *mathRand.Rand // A source for pseudo-random numbers (if selected)
    63  	proxyfsdArgs          []string
    64  	proxyfsdCmd           *exec.Cmd
    65  	proxyfsdCmdWaitChan   chan error
    66  	timeoutChan           chan bool
    67  	trafficCmd            *exec.Cmd
    68  	trafficCmdWaitChan    chan error
    69  	trafficScript         string
    70  	volumeName            string
    71  
    72  	signalExpandedStringMap = map[string]string{"interrupt": "SIGINT(2)", "terminated": "SIGTERM(15)", "killed": "SIGKILL(9)"}
    73  )
    74  
    75  func usage() {
    76  	fmt.Printf("%v {+|-}[<trafficScript>] <volumeName> <confFile> [<confOverride>]*\n", os.Args[0])
    77  	fmt.Println("  where:")
    78  	fmt.Println("    +               indicates to     include halter trigger to halt ProxyFS")
    79  	fmt.Println("    -               indicates to not incluce halter trigger to halt ProxyFS")
    80  	fmt.Println("    <trafficScript> launch trafficScript bash script to generate workload")
    81  	fmt.Println()
    82  	fmt.Println("Note: If trafficScript is supplied, the script should infinitely loop.")
    83  	fmt.Println("      The $1 arg to trafficScript will specify the FUSE MountPoint.")
    84  }
    85  
    86  func main() {
    87  	var (
    88  		confMap                     conf.ConfMap
    89  		confStrings                 []string
    90  		contentsAsStrings           []string
    91  		err                         error
    92  		haltLabelString             string
    93  		haltLabelStringSplit        []string
    94  		httpServerTCPPort           uint16
    95  		httpStatusCode              int
    96  		lenArgs                     int
    97  		mkproxyfsArgs               []string
    98  		mkproxyfsCmd                *exec.Cmd
    99  		nextHalterTriggerIndex      int
   100  		peerSectionName             string
   101  		privateIPAddr               string
   102  		randomHaltAfterCount        uint64
   103  		randomKillDelay             time.Duration
   104  		signalChan                  chan os.Signal
   105  		signalToSend                os.Signal
   106  		triggerBoolAndTrafficScript string
   107  		whoAmI                      string
   108  	)
   109  
   110  	lenArgs = len(os.Args)
   111  	if 1 == lenArgs {
   112  		usage()
   113  		os.Exit(0)
   114  	}
   115  	if 4 > lenArgs {
   116  		usage()
   117  		os.Exit(-1)
   118  	}
   119  
   120  	triggerBoolAndTrafficScript = os.Args[1]
   121  
   122  	if 0 == len(triggerBoolAndTrafficScript) {
   123  		usage()
   124  		os.Exit(-1)
   125  	}
   126  
   127  	switch triggerBoolAndTrafficScript[0] {
   128  	case '+':
   129  		includeHalterTriggers = true
   130  	case '-':
   131  		includeHalterTriggers = false
   132  	default:
   133  		usage()
   134  		os.Exit(-1)
   135  	}
   136  
   137  	trafficScript = triggerBoolAndTrafficScript[1:]
   138  
   139  	volumeName = os.Args[2]
   140  	confFile = os.Args[3]
   141  
   142  	confMap, err = conf.MakeConfMapFromFile(confFile)
   143  	if nil != err {
   144  		log.Fatal(err)
   145  	}
   146  
   147  	mkproxyfsArgs = []string{"-F", volumeName, confFile}
   148  	proxyfsdArgs = []string{confFile}
   149  
   150  	if 4 < lenArgs {
   151  		confStrings = os.Args[4:]
   152  
   153  		err = confMap.UpdateFromStrings(confStrings)
   154  		if nil != err {
   155  			log.Fatalf("failed to apply config overrides: %v", err)
   156  		}
   157  
   158  		mkproxyfsArgs = append(mkproxyfsArgs, confStrings...)
   159  		proxyfsdArgs = append(proxyfsdArgs, confStrings...)
   160  	}
   161  
   162  	// Upgrade confMap if necessary
   163  	err = transitions.UpgradeConfMapIfNeeded(confMap)
   164  	if nil != err {
   165  		log.Fatalf("Failed to upgrade config: %v", err)
   166  	}
   167  
   168  	whoAmI, err = confMap.FetchOptionValueString("Cluster", "WhoAmI")
   169  	if nil != err {
   170  		log.Fatal(err)
   171  	}
   172  
   173  	peerSectionName = "Peer:" + whoAmI
   174  
   175  	privateIPAddr, err = confMap.FetchOptionValueString(peerSectionName, "PrivateIPAddr")
   176  	if nil != err {
   177  		log.Fatal(err)
   178  	}
   179  
   180  	httpServerTCPPort, err = confMap.FetchOptionValueUint16("HTTPServer", "TCPPort")
   181  	if nil != err {
   182  		log.Fatal(err)
   183  	}
   184  
   185  	ipAddrTCPPort = net.JoinHostPort(privateIPAddr, strconv.Itoa(int(httpServerTCPPort)))
   186  
   187  	fuseMountPointName, err = confMap.FetchOptionValueString("Volume:"+volumeName, "FUSEMountPointName")
   188  	if nil != err {
   189  		log.Fatal(err)
   190  	}
   191  
   192  	mkproxyfsCmd = exec.Command("mkproxyfs", mkproxyfsArgs...)
   193  
   194  	err = mkproxyfsCmd.Run()
   195  	if nil != err {
   196  		log.Fatalf("mkproxyfsCmd.Run() failed: %v", err)
   197  	}
   198  
   199  	log.Printf("Call to mkproxyfsCmd.Run() succeeded")
   200  
   201  	proxyfsdCmdWaitChan = make(chan error, 1)
   202  
   203  	cleanDirectoryUnderFUSEMountPointName()
   204  
   205  	launchProxyFSRunSCRUBAndFSCK()
   206  
   207  	log.Printf("Initial call to launchProxyFSRunSCRUBAndFSCK() succeeded")
   208  
   209  	if includeHalterTriggers {
   210  		httpStatusCode, _, contentsAsStrings, err = queryProxyFS(queryMethodGET, "/trigger", "")
   211  		if nil != err {
   212  			log.Printf("queryProxyFS() failed: %v", err)
   213  			stopProxyFS(unix.SIGTERM)
   214  			os.Exit(-1)
   215  		}
   216  		if http.StatusOK != httpStatusCode {
   217  			log.Printf("queryProxyFS() returned unexpected httpStatusCode: %v", httpStatusCode)
   218  			stopProxyFS(unix.SIGTERM)
   219  			os.Exit(-1)
   220  		}
   221  
   222  		haltLabelStrings = make([]string, 0)
   223  
   224  		for _, contentString := range contentsAsStrings {
   225  			haltLabelStringSplit = strings.Split(contentString, " ")
   226  			if 0 == len(haltLabelStringSplit) {
   227  				log.Printf("queryProxyFS() returned unexpected contentString: %v", contentString)
   228  				stopProxyFS(unix.SIGTERM)
   229  				os.Exit(-1)
   230  			}
   231  			haltLabelString = haltLabelStringSplit[0]
   232  			if "" == haltLabelString {
   233  				log.Printf("queryProxyFS() returned unexpected empty contentString")
   234  				stopProxyFS(unix.SIGTERM)
   235  				os.Exit(-1)
   236  			}
   237  
   238  			if !strings.HasPrefix(haltLabelString, "halter.") {
   239  				haltLabelStrings = append(haltLabelStrings, haltLabelString)
   240  			}
   241  		}
   242  
   243  		if 0 == len(haltLabelStrings) {
   244  			log.Printf("No halter.Arm() calls found - disabling")
   245  			includeHalterTriggers = false
   246  		} else {
   247  			log.Printf("Will arm haltLabelStrings:")
   248  			for _, haltLabelString = range haltLabelStrings {
   249  				log.Printf("    %v", haltLabelString)
   250  			}
   251  		}
   252  	} else {
   253  		log.Printf("No halter.Arm() calls scheduled")
   254  	}
   255  
   256  	signalChan = make(chan os.Signal, 1)
   257  
   258  	signal.Notify(signalChan, unix.SIGINT, unix.SIGTERM)
   259  
   260  	timeoutChan = make(chan bool, 1)
   261  
   262  	trafficCmdWaitChan = make(chan error, 1)
   263  
   264  	// Loop through causing ProxyFS to halt via:
   265  	//   SIGINT
   266  	//   SIGTERM
   267  	//   SIGKILL
   268  	//   halter.Trigger() on each of haltLabelStrings
   269  	// until SIGINT or SIGTERM
   270  
   271  	signalToSend = unix.SIGINT
   272  
   273  	for {
   274  		if nil == signalToSend {
   275  			randomHaltAfterCount = proxyfsdRandomHaltAfterCount()
   276  			log.Printf("Arming trigger %v with haltAfterCount == %v", haltLabelStrings[nextHalterTriggerIndex], randomHaltAfterCount)
   277  			httpStatusCode, _, _, err = queryProxyFS(queryMethodPOST, "/trigger/"+haltLabelStrings[nextHalterTriggerIndex]+"?count="+strconv.FormatUint(randomHaltAfterCount, 10), "")
   278  			if nil != err {
   279  				log.Printf("queryProxyFS() failed: %v", err)
   280  				stopProxyFS(unix.SIGTERM)
   281  				os.Exit(-1)
   282  			}
   283  			if http.StatusNoContent != httpStatusCode {
   284  				log.Printf("queryProxyFS() returned unexpected httpStatusCode: %v", httpStatusCode)
   285  				stopProxyFS(unix.SIGTERM)
   286  				os.Exit(-1)
   287  			}
   288  		} else {
   289  			randomKillDelay = proxyfsdRandomKillDelay()
   290  			log.Printf("Will fire %v after %v", signalExpandedStringMap[signalToSend.String()], randomKillDelay)
   291  			go timeoutWaiter(randomKillDelay)
   292  		}
   293  
   294  		launchTrafficScript()
   295  
   296  		select {
   297  		case _ = <-signalChan:
   298  			log.Printf("Received SIGINT or SIGTERM... cleanly shutting down ProxyFS")
   299  			stopTrafficScript()
   300  			stopProxyFS(unix.SIGTERM)
   301  			os.Exit(0)
   302  		case _ = <-timeoutChan:
   303  			log.Printf("Sending %v to ProxyFS", signalExpandedStringMap[signalToSend.String()])
   304  			stopProxyFS(signalToSend)
   305  			stopTrafficScript()
   306  		case err = <-proxyfsdCmdWaitChan:
   307  			log.Printf("ProxyFS has halted due to trigger or other failure")
   308  			stopTrafficScript()
   309  		case err = <-trafficCmdWaitChan:
   310  			log.Printf("trafficScript unexpectedly finished/failed: %v", err)
   311  			stopProxyFS(unix.SIGTERM)
   312  			os.Exit(-1)
   313  		}
   314  
   315  		cleanDirectoryUnderFUSEMountPointName()
   316  
   317  		launchProxyFSRunSCRUBAndFSCK()
   318  
   319  		switch signalToSend {
   320  		case unix.SIGINT:
   321  			signalToSend = unix.SIGTERM
   322  		case unix.SIGTERM:
   323  			signalToSend = unix.SIGKILL
   324  		case unix.SIGKILL:
   325  			if includeHalterTriggers {
   326  				signalToSend = nil
   327  				nextHalterTriggerIndex = 0
   328  			} else {
   329  				signalToSend = unix.SIGINT
   330  			}
   331  		case nil:
   332  			nextHalterTriggerIndex++
   333  			if len(haltLabelStrings) == nextHalterTriggerIndex {
   334  				signalToSend = unix.SIGINT
   335  			}
   336  		default:
   337  			log.Printf("Logic error... unexpected signalToSend: %v", signalToSend)
   338  			stopTrafficScript()
   339  			stopProxyFS(unix.SIGTERM)
   340  			os.Exit(-1)
   341  		}
   342  	}
   343  }
   344  
   345  func timeoutWaiter(randomKillDelay time.Duration) {
   346  	time.Sleep(randomKillDelay)
   347  	timeoutChan <- true
   348  }
   349  
   350  func trafficCmdWaiter() {
   351  	trafficCmdWaitChan <- trafficCmd.Wait()
   352  }
   353  
   354  func stopTrafficScript() {
   355  	var (
   356  		err          error
   357  		pid          int
   358  		pidSliceLast []int
   359  		pidSliceNow  []int
   360  		umountCmd    *exec.Cmd
   361  	)
   362  
   363  	if "" != trafficScript {
   364  		pidSliceNow = pstree(trafficCmd.Process.Pid)
   365  
   366  		// Send SIGSTOP to all pids in pidSliceNow to prevent more trafficScript processes from being created
   367  
   368  		for {
   369  			for _, pid = range pidSliceNow {
   370  				err = unix.Kill(pid, unix.SIGSTOP)
   371  				if nil != err {
   372  					log.Printf("INFO: unix.Kill(%v, unix.SIGSTOP) failed: %v", pid, err)
   373  				}
   374  			}
   375  			pidSliceLast = pidSliceNow
   376  			pidSliceNow = pstree(trafficCmd.Process.Pid)
   377  			if pidSliceEqual(pidSliceLast, pidSliceNow) {
   378  				break
   379  			}
   380  		}
   381  
   382  		// Send SIGKILL to all pids in pidSliceNow to actually kill all trafficScript processes
   383  
   384  		for {
   385  			for _, pid = range pidSliceNow {
   386  				err = unix.Kill(pid, unix.SIGKILL)
   387  				if nil != err {
   388  					log.Printf("INFO: unix.Kill(%v, unix.SIGKILL) failed: %v", pid, err)
   389  				}
   390  			}
   391  			pidSliceLast = pidSliceNow
   392  			pidSliceNow = pstree(trafficCmd.Process.Pid)
   393  			if pidSliceEqual(pidSliceLast, pidSliceNow) {
   394  				break
   395  			}
   396  		}
   397  
   398  		// Force an unmount of fewMountPointName incase any processes of trafficScript are hung on if
   399  
   400  		umountCmd = exec.Command("fusermount", "-u", fuseMountPointName)
   401  
   402  		err = umountCmd.Run()
   403  		if nil != err {
   404  			log.Printf("INFO: umountCmd.Run() failed: %v", err)
   405  		}
   406  
   407  		// Finally, await indicated exit of trafficScript
   408  
   409  		_ = <-trafficCmdWaitChan
   410  	}
   411  }
   412  
   413  func launchTrafficScript() {
   414  	var (
   415  		err error
   416  	)
   417  
   418  	if "" != trafficScript {
   419  		log.Printf("Launching trafficScript: bash %v %v", trafficScript, fuseMountPointName)
   420  
   421  		trafficCmd = exec.Command("bash", trafficScript, fuseMountPointName)
   422  
   423  		err = trafficCmd.Start()
   424  		if nil != err {
   425  			log.Fatalf("trafficCmd.Start() failed: %v", err)
   426  		}
   427  
   428  		go trafficCmdWaiter()
   429  	}
   430  }
   431  
   432  func proxyfsdCmdWaiter() {
   433  	proxyfsdCmdWaitChan <- proxyfsdCmd.Wait()
   434  }
   435  
   436  func stopProxyFS(signalToSend os.Signal) {
   437  	var (
   438  		err error
   439  	)
   440  
   441  	err = proxyfsdCmd.Process.Signal(signalToSend)
   442  	if nil != err {
   443  		log.Fatalf("proxyfsdCmd.Process.Signal(signalToSend) failed: %v", err)
   444  	}
   445  	_ = <-proxyfsdCmdWaitChan
   446  }
   447  
   448  func launchProxyFSRunSCRUBAndFSCK() {
   449  	var (
   450  		contentsAsStrings []string
   451  		err               error
   452  		fsckJob           httpserver.JobStatusJSONPackedStruct
   453  		fsckJobError      string
   454  		httpStatusCode    int
   455  		locationURL       string
   456  		polling           bool
   457  		scrubJob          httpserver.JobStatusJSONPackedStruct
   458  		scrubJobError     string
   459  	)
   460  
   461  	log.Printf("Launching ProxyFS and performing SCRUB & FSCK of %v", volumeName)
   462  
   463  	proxyfsdCmd = exec.Command("proxyfsd", proxyfsdArgs...)
   464  
   465  	err = proxyfsdCmd.Start()
   466  	if nil != err {
   467  		log.Fatalf("proxyfsdCmd.Start() failed: %v", err)
   468  	}
   469  
   470  	go proxyfsdCmdWaiter()
   471  
   472  	polling = true
   473  	for polling {
   474  		time.Sleep(proxyfsdPollDelay)
   475  
   476  		httpStatusCode, locationURL, _, err = queryProxyFS(queryMethodPOST, "/volume/"+volumeName+"/scrub-job", "")
   477  		if nil == err {
   478  			polling = false
   479  		}
   480  	}
   481  
   482  	if http.StatusCreated != httpStatusCode {
   483  		log.Printf("queryProxyFS(queryMethodPOST,\"/volume/%v/scrub-job\",) returned unexpected httpStatusCode: %v", volumeName, httpStatusCode)
   484  		stopProxyFS(unix.SIGTERM)
   485  		os.Exit(-1)
   486  	}
   487  
   488  	polling = true
   489  	for polling {
   490  		time.Sleep(proxyfsdPollDelay)
   491  
   492  		httpStatusCode, _, contentsAsStrings, err = queryProxyFS(queryMethodGET, locationURL+"?compact=true", "application/json")
   493  		if nil != err {
   494  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) failed: %v", locationURL, err)
   495  			stopProxyFS(unix.SIGTERM)
   496  			os.Exit(-1)
   497  		}
   498  		if http.StatusOK != httpStatusCode {
   499  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned unexpected httpStatusCode: %v", locationURL, httpStatusCode)
   500  			stopProxyFS(unix.SIGTERM)
   501  			os.Exit(-1)
   502  		}
   503  		if 1 != len(contentsAsStrings) {
   504  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned unexpected len(contentsAsStrings): %v", locationURL, len(contentsAsStrings))
   505  		}
   506  
   507  		err = json.Unmarshal([]byte(contentsAsStrings[0]), &scrubJob)
   508  		if nil != err {
   509  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned undecodable content: %v (err == %v)", locationURL, contentsAsStrings[0], err)
   510  			stopProxyFS(unix.SIGTERM)
   511  			os.Exit(-1)
   512  		}
   513  		if "" == scrubJob.StartTime {
   514  			log.Printf("scrubJob unexpectantly missing StartTime value")
   515  			stopProxyFS(unix.SIGTERM)
   516  			os.Exit(-1)
   517  		}
   518  		if "" != scrubJob.HaltTime {
   519  			log.Printf("scrubJob contained unexpected HaltTime value: %v", scrubJob.HaltTime)
   520  			stopProxyFS(unix.SIGTERM)
   521  			os.Exit(-1)
   522  		}
   523  
   524  		if "" != scrubJob.DoneTime {
   525  			polling = false
   526  		}
   527  	}
   528  
   529  	if 0 < len(scrubJob.ErrorList) {
   530  		if 1 == len(scrubJob.ErrorList) {
   531  			log.Printf("scrubJob contained unexpected error: %v", scrubJob.ErrorList[0])
   532  		} else {
   533  			log.Printf("scrubJob contained unexpected errors:")
   534  			for _, scrubJobError = range scrubJob.ErrorList {
   535  				log.Printf("  %v", scrubJobError)
   536  			}
   537  		}
   538  		stopProxyFS(unix.SIGTERM)
   539  		os.Exit(-1)
   540  	}
   541  
   542  	httpStatusCode, locationURL, _, err = queryProxyFS(queryMethodPOST, "/volume/"+volumeName+"/fsck-job", "")
   543  	if nil != err {
   544  		log.Printf("queryProxyFS(queryMethodPOST,\"/volume/%v/fsck-job\",) failed: %v", volumeName, err)
   545  		stopProxyFS(unix.SIGTERM)
   546  		os.Exit(-1)
   547  	}
   548  	if http.StatusCreated != httpStatusCode {
   549  		log.Printf("queryProxyFS(queryMethodPOST,\"/volume/%v/fsck-job\",) returned unexpected httpStatusCode: %v", volumeName, httpStatusCode)
   550  		stopProxyFS(unix.SIGTERM)
   551  		os.Exit(-1)
   552  	}
   553  
   554  	polling = true
   555  	for polling {
   556  		time.Sleep(proxyfsdPollDelay)
   557  
   558  		httpStatusCode, _, contentsAsStrings, err = queryProxyFS(queryMethodGET, locationURL+"?compact=true", "application/json")
   559  		if nil != err {
   560  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) failed: %v", locationURL, err)
   561  			stopProxyFS(unix.SIGTERM)
   562  			os.Exit(-1)
   563  		}
   564  		if http.StatusOK != httpStatusCode {
   565  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned unexpected httpStatusCode: %v", locationURL, httpStatusCode)
   566  			stopProxyFS(unix.SIGTERM)
   567  			os.Exit(-1)
   568  		}
   569  		if 1 != len(contentsAsStrings) {
   570  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned unexpected len(contentsAsStrings): %v", locationURL, len(contentsAsStrings))
   571  		}
   572  
   573  		err = json.Unmarshal([]byte(contentsAsStrings[0]), &fsckJob)
   574  		if nil != err {
   575  			log.Printf("queryProxyFS(queryMethodGET,\"%v?compact=true\",) returned undecodable content: %v (err == %v)", locationURL, contentsAsStrings[0], err)
   576  			stopProxyFS(unix.SIGTERM)
   577  			os.Exit(-1)
   578  		}
   579  		if "" == fsckJob.StartTime {
   580  			log.Printf("fsckJob unexpectantly missing StartTime value")
   581  			stopProxyFS(unix.SIGTERM)
   582  			os.Exit(-1)
   583  		}
   584  		if "" != fsckJob.HaltTime {
   585  			log.Printf("fsckJob contained unexpected HaltTime value: %v", fsckJob.HaltTime)
   586  			stopProxyFS(unix.SIGTERM)
   587  			os.Exit(-1)
   588  		}
   589  
   590  		if "" != fsckJob.DoneTime {
   591  			polling = false
   592  		}
   593  	}
   594  
   595  	if 0 < len(fsckJob.ErrorList) {
   596  		if 1 == len(fsckJob.ErrorList) {
   597  			log.Printf("fsckJob contained unexpected error: %v", fsckJob.ErrorList[0])
   598  		} else {
   599  			log.Printf("fsckJob contained unexpected errors:")
   600  			for _, fsckJobError = range fsckJob.ErrorList {
   601  				log.Printf("  %v", fsckJobError)
   602  			}
   603  		}
   604  		stopProxyFS(unix.SIGTERM)
   605  		os.Exit(-1)
   606  	}
   607  
   608  	log.Printf("ProxyFS launched; SCRUB & FSCK of %v reported no errors", volumeName)
   609  }
   610  
   611  func queryProxyFS(queryMethod queryMethodType, queryURL string, acceptHeader string) (httpStatusCode int, locationURL string, contentsAsStrings []string, err error) {
   612  	var (
   613  		client              *http.Client
   614  		contentsAsByteSlice []byte
   615  		queryURLWithHost    string
   616  		request             *http.Request
   617  		response            *http.Response
   618  	)
   619  
   620  	queryURLWithHost = "http://" + ipAddrTCPPort + queryURL
   621  
   622  	switch queryMethod {
   623  	case queryMethodGET:
   624  		request, err = http.NewRequest("GET", queryURLWithHost, nil)
   625  	case queryMethodPOST:
   626  		request, err = http.NewRequest("POST", queryURLWithHost, nil)
   627  	default:
   628  		log.Fatalf("queryProxyFS(queryMethod==%v,,) invalid", queryMethod)
   629  	}
   630  
   631  	if "" != acceptHeader {
   632  		request.Header.Add("Accept", acceptHeader)
   633  	}
   634  
   635  	client = &http.Client{}
   636  
   637  	response, err = client.Do(request)
   638  	if nil != err {
   639  		return
   640  	}
   641  
   642  	defer response.Body.Close()
   643  
   644  	httpStatusCode = response.StatusCode
   645  
   646  	locationURL = response.Header.Get("Location")
   647  
   648  	contentsAsByteSlice, err = ioutil.ReadAll(response.Body)
   649  	if nil != err {
   650  		return
   651  	}
   652  
   653  	contentsAsStrings = strings.Split(string(contentsAsByteSlice), "\n")
   654  	if "" == contentsAsStrings[len(contentsAsStrings)-1] {
   655  		contentsAsStrings = contentsAsStrings[:len(contentsAsStrings)-1]
   656  	}
   657  
   658  	return
   659  }
   660  
   661  func proxyfsdRandomHaltAfterCount() (haltAfterCount uint64) {
   662  	var (
   663  		bigN *big.Int
   664  		bigR *big.Int
   665  		err  error
   666  	)
   667  
   668  	if pseudoRandom {
   669  		if nil == mathRandSource {
   670  			mathRandSource = mathRand.New(mathRand.NewSource(pseudoRandomSeed))
   671  		}
   672  		haltAfterCount = uint64(mathRandSource.Int63n(int64(proxyfsdHalterMaxHaltAfterCount-proxyfsdHalterMinHaltAfterCount)+1)) + proxyfsdHalterMinHaltAfterCount
   673  	} else {
   674  		bigN = big.NewInt(int64(proxyfsdHalterMaxHaltAfterCount-proxyfsdHalterMinHaltAfterCount) + 1)
   675  		bigR, err = cryptoRand.Int(cryptoRand.Reader, bigN)
   676  		if nil != err {
   677  			log.Fatalf("cryptoRand.Int(cryptoRand.Reader, bigN) failed: %v", err)
   678  		}
   679  		haltAfterCount = bigR.Uint64() + proxyfsdHalterMinHaltAfterCount
   680  	}
   681  
   682  	return
   683  }
   684  
   685  func proxyfsdRandomKillDelay() (killDelay time.Duration) {
   686  	var (
   687  		bigN *big.Int
   688  		bigR *big.Int
   689  		err  error
   690  	)
   691  
   692  	if pseudoRandom {
   693  		if nil == mathRandSource {
   694  			mathRandSource = mathRand.New(mathRand.NewSource(pseudoRandomSeed))
   695  		}
   696  		killDelay = time.Duration(mathRandSource.Int63n(int64(proxyfsdMaxKillDelay-proxyfsdMinKillDelay)+1)) + proxyfsdMinKillDelay
   697  	} else {
   698  		bigN = big.NewInt(int64(proxyfsdMaxKillDelay-proxyfsdMinKillDelay) + 1)
   699  		bigR, err = cryptoRand.Int(cryptoRand.Reader, bigN)
   700  		if nil != err {
   701  			log.Fatalf("cryptoRand.Int(cryptoRand.Reader, bigN) failed: %v", err)
   702  		}
   703  		killDelay = time.Duration(bigR.Uint64()) + proxyfsdMinKillDelay
   704  	}
   705  
   706  	return
   707  }
   708  
   709  func pstree(topPid int) (pidSlice []int) {
   710  	var (
   711  		err                 error
   712  		ok                  bool
   713  		pid                 int
   714  		pidChildren         []int
   715  		pidChildrenMap      map[int][]int
   716  		pidList             *list.List
   717  		pidOnList           *list.Element
   718  		ppid                int
   719  		psOutputByteSlice   []byte
   720  		psOutputBufioReader *bufio.Reader
   721  		psOutputBytesReader *bytes.Reader
   722  		psOutputLine        string
   723  		sscanfN             int
   724  	)
   725  
   726  	psOutputByteSlice, err = exec.Command("ps", "-e", "-o", "pid,ppid").Output()
   727  	if nil != err {
   728  		log.Fatalf("exec.Command(\"ps\", \"-e\", \"-o\", \"pid,ppid\").Output failed: %v", err)
   729  	}
   730  
   731  	psOutputBytesReader = bytes.NewReader(psOutputByteSlice)
   732  	psOutputBufioReader = bufio.NewReader(psOutputBytesReader)
   733  
   734  	pidChildrenMap = make(map[int][]int)
   735  
   736  	for {
   737  		psOutputLine, err = psOutputBufioReader.ReadString(byte(0x0A))
   738  		if nil != err {
   739  			break
   740  		}
   741  		sscanfN, err = fmt.Sscanf(psOutputLine, "%d %d\n", &pid, &ppid)
   742  		if nil != err {
   743  			continue
   744  		}
   745  		if 2 != sscanfN {
   746  			continue
   747  		}
   748  
   749  		pidChildren, ok = pidChildrenMap[ppid]
   750  		if !ok {
   751  			pidChildren = make([]int, 0, 1)
   752  		}
   753  		pidChildren = append(pidChildren, pid)
   754  		pidChildrenMap[ppid] = pidChildren
   755  	}
   756  
   757  	pidList = list.New()
   758  
   759  	pstreeStep(topPid, pidChildrenMap, pidList)
   760  
   761  	pidSlice = make([]int, 0, pidList.Len())
   762  
   763  	for pidOnList = pidList.Front(); nil != pidOnList; pidOnList = pidOnList.Next() {
   764  		pid, ok = pidOnList.Value.(int)
   765  		if !ok {
   766  			log.Fatalf("pidOnList.Value.(int) failed")
   767  		}
   768  		pidSlice = append(pidSlice, pid)
   769  	}
   770  
   771  	return
   772  }
   773  
   774  func pstreeStep(ppid int, pidChildrenMap map[int][]int, pidList *list.List) {
   775  	var (
   776  		ok          bool
   777  		pid         int
   778  		pidChildren []int
   779  	)
   780  
   781  	_ = pidList.PushBack(ppid)
   782  
   783  	pidChildren, ok = pidChildrenMap[ppid]
   784  	if !ok {
   785  		return
   786  	}
   787  
   788  	for _, pid = range pidChildren {
   789  		pstreeStep(pid, pidChildrenMap, pidList)
   790  	}
   791  }
   792  
   793  func pidSliceEqual(pidSlice1 []int, pidSlice2 []int) (equal bool) {
   794  	var (
   795  		ok           bool
   796  		pid          int
   797  		pidSlice1Map map[int]struct{} // Go's version of a "set"
   798  	)
   799  
   800  	if len(pidSlice1) != len(pidSlice2) {
   801  		equal = false
   802  		return
   803  	}
   804  
   805  	pidSlice1Map = make(map[int]struct{})
   806  
   807  	for _, pid = range pidSlice1 {
   808  		pidSlice1Map[pid] = struct{}{}
   809  	}
   810  
   811  	for _, pid = range pidSlice2 {
   812  		_, ok = pidSlice1Map[pid]
   813  		if !ok {
   814  			equal = false
   815  			return
   816  		}
   817  	}
   818  
   819  	equal = true
   820  	return
   821  }
   822  
   823  func cleanDirectoryUnderFUSEMountPointName() {
   824  	var (
   825  		dir        *os.File
   826  		err        error
   827  		name       string
   828  		nameJoined string
   829  		names      []string
   830  		umountCmd  *exec.Cmd
   831  	)
   832  
   833  	for {
   834  		time.Sleep(openDirPollDelay)
   835  
   836  		dir, err = os.Open(fuseMountPointName)
   837  		if nil == err {
   838  			break
   839  		}
   840  
   841  		log.Printf("Retrying os.Open(\"%v\") after \"fusermount -u\" due to err == %v", fuseMountPointName, err)
   842  
   843  		umountCmd = exec.Command("fusermount", "-u", fuseMountPointName)
   844  
   845  		err = umountCmd.Run()
   846  		if nil != err {
   847  			log.Printf("umountCmd.Run() failed: %v", err)
   848  		}
   849  	}
   850  
   851  	names, err = dir.Readdirnames(-1)
   852  	if nil != err {
   853  		_ = dir.Close()
   854  		log.Fatalf("dir.Readdirnames(-1) failed: %v", err)
   855  	}
   856  
   857  	err = dir.Close()
   858  	if nil != err {
   859  		log.Fatalf("dir.Close() failed: %v", err)
   860  	}
   861  
   862  	for _, name = range names {
   863  		nameJoined = filepath.Join(fuseMountPointName, name)
   864  		err = os.RemoveAll(nameJoined)
   865  		if nil != err {
   866  			log.Fatalf("os.RemoveAll(%v) failed: %v", nameJoined, err)
   867  		}
   868  	}
   869  }