[
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# popular temporaries\n.err\n.out\n.diff\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"git-hooks\"]\n\tpath = git-hooks\n\turl = https://github.com/nightlyone/git-hooks\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\narch:\n  - ppc64le\n  - amd64\ngo:\n  - 1.13\n  - 1.14\n  - tip\n\n# Only test commits to production branch and all pull requests\nbranches:\n  only:\n    - master\n\nmatrix:\n  allow_failures:\n  - go: tip\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012 Ingo Oeser\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "lockfile\n=========\nHandle locking via pid files.\n\n[![Build Status Unix][1]][2]\n[![Build status Windows][3]][4]\n\n[1]: https://secure.travis-ci.org/nightlyone/lockfile.png\n[2]: https://travis-ci.org/nightlyone/lockfile\n[3]: https://ci.appveyor.com/api/projects/status/7mojkmauj81uvp8u/branch/master?svg=true\n[4]: https://ci.appveyor.com/project/nightlyone/lockfile/branch/master\n\n\n\ninstall\n-------\nInstall [Go 1][5], either [from source][6] or [with a prepackaged binary][7].\nFor Windows suport, Go 1.4 or newer is required.\n\nThen run\n\n\tgo get github.com/nightlyone/lockfile\n\n[5]: http://golang.org\n[6]: http://golang.org/doc/install/source\n[7]: http://golang.org/doc/install\n\nLICENSE\n-------\nMIT\n\ndocumentation\n-------------\n[package documentation at godoc.org](http://godoc.org/github.com/nightlyone/lockfile)\n\ninstall\n-------------------\n\tgo get github.com/nightlyone/lockfile\n\n\ncontributing\n============\n\nContributions are welcome. Please open an issue or send me a pull request for a dedicated branch.\nMake sure the git commit hooks show it works.\n\ngit commit hooks\n-----------------------\nenable commit hooks via\n\n        cd .git ; rm -rf hooks; ln -s ../git-hooks hooks ; cd ..\n\n"
  },
  {
    "path": "appveyor.yml",
    "content": "clone_folder: c:\\gopath\\src\\github.com\\nightlyone\\lockfile\n\nenvironment:\n  GOPATH: c:\\gopath\n\ninstall:\n  - go version\n  - go env\n  - go get -v -t ./...\n\nbuild_script:\n  - go test -v ./...\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/nightlyone/lockfile\n\ngo 1.11\n"
  },
  {
    "path": "lockfile.go",
    "content": "// Package lockfile handles pid file based locking.\n// While a sync.Mutex helps against concurrency issues within a single process,\n// this package is designed to help against concurrency issues between cooperating processes\n// or serializing multiple invocations of the same process. You can also combine sync.Mutex\n// with Lockfile in order to serialize an action between different goroutines in a single program\n// and also multiple invocations of this program.\npackage lockfile\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// Lockfile is a pid file which can be locked\ntype Lockfile string\n\n// TemporaryError is a type of error where a retry after a random amount of sleep should help to mitigate it.\ntype TemporaryError string\n\nfunc (t TemporaryError) Error() string { return string(t) }\n\n// Temporary returns always true.\n// It exists, so you can detect it via\n//\tif te, ok := err.(interface{ Temporary() bool }); ok {\n//\t\tfmt.Println(\"I am a temporary error situation, so wait and retry\")\n//\t}\nfunc (t TemporaryError) Temporary() bool { return true }\n\n// Various errors returned by this package\nvar (\n\tErrBusy          = TemporaryError(\"Locked by other process\")             // If you get this, retry after a short sleep might help\n\tErrNotExist      = TemporaryError(\"Lockfile created, but doesn't exist\") // If you get this, retry after a short sleep might help\n\tErrNeedAbsPath   = errors.New(\"Lockfiles must be given as absolute path names\")\n\tErrInvalidPid    = errors.New(\"Lockfile contains invalid pid for system\")\n\tErrDeadOwner     = errors.New(\"Lockfile contains pid of process not existent on this system anymore\")\n\tErrRogueDeletion = errors.New(\"Lockfile owned by me has been removed unexpectedly\")\n)\n\n// New describes a new filename located at the given absolute path.\nfunc New(path string) (Lockfile, error) {\n\tif !filepath.IsAbs(path) {\n\t\treturn Lockfile(\"\"), ErrNeedAbsPath\n\t}\n\n\treturn Lockfile(path), nil\n}\n\n// GetOwner returns who owns the lockfile.\nfunc (l Lockfile) GetOwner() (*os.Process, error) {\n\tname := string(l)\n\n\t// Ok, see, if we have a stale lockfile here\n\tcontent, err := ioutil.ReadFile(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// try hard for pids. If no pid, the lockfile is junk anyway and we delete it.\n\tpid, err := scanPidLine(content)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trunning, err := isRunning(pid)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif running {\n\t\tproc, err := os.FindProcess(pid)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\treturn proc, nil\n\t}\n\n\treturn nil, ErrDeadOwner\n}\n\n// TryLock tries to own the lock.\n// It Returns nil, if successful and and error describing the reason, it didn't work out.\n// Please note, that existing lockfiles containing pids of dead processes\n// and lockfiles containing no pid at all are simply deleted.\nfunc (l Lockfile) TryLock() error {\n\tname := string(l)\n\n\t// This has been checked by New already. If we trigger here,\n\t// the caller didn't use New and re-implemented it's functionality badly.\n\t// So panic, that he might find this easily during testing.\n\tif !filepath.IsAbs(name) {\n\t\tpanic(ErrNeedAbsPath)\n\t}\n\n\ttmplock, cleanup, err := makePidFile(name, os.Getpid())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer cleanup()\n\n\t// EEXIST and similar error codes, caught by os.IsExist, are intentionally ignored,\n\t// as it means that someone was faster creating this link\n\t// and ignoring this kind of error is part of the algorithm.\n\t// Then we will probably fail the pid owner check later, if this process is still alive.\n\t// We cannot ignore ALL errors, since failure to support hard links, disk full\n\t// as well as many other errors can happen to a filesystem operation\n\t// and we really want to abort on those.\n\tif err := os.Link(tmplock, name); err != nil {\n\t\tif !os.IsExist(err) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tfiTmp, err := os.Lstat(tmplock)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfiLock, err := os.Lstat(name)\n\tif err != nil {\n\t\t// tell user that a retry would be a good idea\n\t\tif os.IsNotExist(err) {\n\t\t\treturn ErrNotExist\n\t\t}\n\n\t\treturn err\n\t}\n\n\t// Success\n\tif os.SameFile(fiTmp, fiLock) {\n\t\treturn nil\n\t}\n\n\tproc, err := l.GetOwner()\n\tswitch err {\n\tdefault:\n\t\t// Other errors -> defensively fail and let caller handle this\n\t\treturn err\n\tcase nil:\n\t\tif proc.Pid != os.Getpid() {\n\t\t\treturn ErrBusy\n\t\t}\n\tcase ErrDeadOwner, ErrInvalidPid: // cases we can fix below\n\t}\n\n\t// clean stale/invalid lockfile\n\terr = os.Remove(name)\n\tif err != nil {\n\t\t// If it doesn't exist, then it doesn't matter who removed it.\n\t\tif !os.IsNotExist(err) {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// now that the stale lockfile is gone, let's recurse\n\treturn l.TryLock()\n}\n\n// Unlock a lock again, if we owned it. Returns any error that happened during release of lock.\nfunc (l Lockfile) Unlock() error {\n\tproc, err := l.GetOwner()\n\tswitch err {\n\tcase ErrInvalidPid, ErrDeadOwner:\n\t\treturn ErrRogueDeletion\n\tcase nil:\n\t\tif proc.Pid == os.Getpid() {\n\t\t\t// we really own it, so let's remove it.\n\t\t\treturn os.Remove(string(l))\n\t\t}\n\t\t// Not owned by me, so don't delete it.\n\t\treturn ErrRogueDeletion\n\tdefault:\n\t\t// This is an application error or system error.\n\t\t// So give a better error for logging here.\n\t\tif os.IsNotExist(err) {\n\t\t\treturn ErrRogueDeletion\n\t\t}\n\t\t// Other errors -> defensively fail and let caller handle this\n\t\treturn err\n\t}\n}\n\nfunc scanPidLine(content []byte) (int, error) {\n\tif len(content) == 0 {\n\t\treturn 0, ErrInvalidPid\n\t}\n\n\tvar pid int\n\tif _, err := fmt.Sscanln(string(content), &pid); err != nil {\n\t\treturn 0, ErrInvalidPid\n\t}\n\n\tif pid <= 0 {\n\t\treturn 0, ErrInvalidPid\n\t}\n\n\treturn pid, nil\n}\n\nfunc makePidFile(name string, pid int) (tmpname string, cleanup func(), err error) {\n\ttmplock, err := ioutil.TempFile(filepath.Dir(name), filepath.Base(name)+\".\")\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tcleanup = func() {\n\t\t_ = tmplock.Close()\n\t\t_ = os.Remove(tmplock.Name())\n\t}\n\n\tif _, err := io.WriteString(tmplock, fmt.Sprintf(\"%d\\n\", pid)); err != nil {\n\t\tcleanup() // Do cleanup here, so call doesn't have to.\n\t\treturn \"\", nil, err\n\t}\n\n\treturn tmplock.Name(), cleanup, nil\n}\n"
  },
  {
    "path": "lockfile_test.go",
    "content": "package lockfile\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc ExampleLockfile() {\n\tlock, err := New(filepath.Join(os.TempDir(), \"lock.me.now.lck\"))\n\tif err != nil {\n\t\tfmt.Printf(\"Cannot init lock. reason: %v\", err)\n\t\tpanic(err) // handle properly please!\n\t}\n\n\t// Error handling is essential, as we only try to get the lock.\n\tif err = lock.TryLock(); err != nil {\n\t\tfmt.Printf(\"Cannot lock %q, reason: %v\", lock, err)\n\t\tpanic(err) // handle properly please!\n\t}\n\n\tdefer func() {\n\t\tif err := lock.Unlock(); err != nil {\n\t\t\tfmt.Printf(\"Cannot unlock %q, reason: %v\", lock, err)\n\t\t\tpanic(err) // handle properly please!\n\t\t}\n\t}()\n\n\tfmt.Println(\"Do stuff under lock\")\n\t// Output: Do stuff under lock\n}\n\nfunc TestBasicLockUnlock(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fail()\n\t\tfmt.Println(\"Error making lockfile: \", err)\n\t\treturn\n\t}\n\n\terr = lf.TryLock()\n\tif err != nil {\n\t\tt.Fail()\n\t\tfmt.Println(\"Error locking lockfile: \", err)\n\t\treturn\n\t}\n\n\terr = lf.Unlock()\n\tif err != nil {\n\t\tt.Fail()\n\t\tfmt.Println(\"Error unlocking lockfile: \", err)\n\t\treturn\n\t}\n}\n\nfunc GetDeadPID() int {\n\t// I have no idea how windows handles large PIDs, or if they even exist.\n\t// So limit it to be less or equal to 4096 to be safe.\n\tconst maxPid = 4095\n\n\t// limited iteration, so we finish one day\n\tseen := map[int]bool{}\n\tfor len(seen) < maxPid {\n\t\tpid := rand.Intn(maxPid + 1) // see https://godoc.org/math/rand#Intn why\n\t\tif seen[pid] {\n\t\t\tcontinue\n\t\t}\n\n\t\tseen[pid] = true\n\n\t\trunning, err := isRunning(pid)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"Error checking PID: \", err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !running {\n\t\t\treturn pid\n\t\t}\n\t}\n\tpanic(fmt.Sprintf(\"all pids lower %d are used, cannot test this\", maxPid))\n}\n\nfunc TestBusy(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tpid := os.Getppid()\n\n\tif err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+\"\\n\"), 0666); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tdefer os.Remove(path)\n\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tgot := lf.TryLock()\n\tif got != ErrBusy {\n\t\tt.Fatalf(\"expected error %q, got %v\", ErrBusy, got)\n\t\treturn\n\t}\n}\n\nfunc TestRogueDeletion(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\terr = lf.TryLock()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\terr = os.Remove(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tgot := lf.Unlock()\n\tif got != ErrRogueDeletion {\n\t\tt.Fatalf(\"unexpected error: %v\", got)\n\t\treturn\n\t}\n}\n\nfunc TestRogueDeletionDeadPid(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\terr = lf.TryLock()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tpid := GetDeadPID()\n\tif err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+\"\\n\"), 0666); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tdefer os.Remove(path)\n\n\terr = lf.Unlock()\n\tif err != ErrRogueDeletion {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\tif _, err := os.Stat(path); err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\tcontent, _ := ioutil.ReadFile(path)\n\t\t\tt.Fatalf(\"lockfile %q (%q) should not be deleted by us, if we didn't create it\", path, content)\n\t\t}\n\t\tt.Fatalf(\"unexpected error %v\", err)\n\t}\n}\n\nfunc TestRemovesStaleLockOnDeadOwner(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\tpid := GetDeadPID()\n\tif err := ioutil.WriteFile(path, []byte(strconv.Itoa(pid)+\"\\n\"), 0666); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\terr = lf.TryLock()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tif err := lf.Unlock(); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n}\n\nfunc TestInvalidPidLeadToReplacedLockfileAndSuccess(t *testing.T) {\n\tpath, err := filepath.Abs(\"test_lockfile.pid\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tif err := ioutil.WriteFile(path, []byte(\"\\n\"), 0666); err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tdefer os.Remove(path)\n\n\tlf, err := New(path)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t\treturn\n\t}\n\n\tif err := lf.TryLock(); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t\treturn\n\t}\n\n\t// now check if file exists and contains the correct content\n\tgot, err := ioutil.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error %v\", err)\n\t\treturn\n\t}\n\twant := fmt.Sprintf(\"%d\\n\", os.Getpid())\n\tif string(got) != want {\n\t\tt.Fatalf(\"got %q, want %q\", got, want)\n\t}\n}\n\nfunc TestScanPidLine(t *testing.T) {\n\ttests := [...]struct {\n\t\tinput []byte\n\t\tpid   int\n\t\txfail error\n\t}{\n\t\t{xfail: ErrInvalidPid},\n\t\t{input: []byte(\"\"), xfail: ErrInvalidPid},\n\t\t{input: []byte(\"\\n\"), xfail: ErrInvalidPid},\n\t\t{input: []byte(\"-1\\n\"), xfail: ErrInvalidPid},\n\t\t{input: []byte(\"0\\n\"), xfail: ErrInvalidPid},\n\t\t{input: []byte(\"a\\n\"), xfail: ErrInvalidPid},\n\t\t{input: []byte(\"1\\n\"), pid: 1},\n\t}\n\n\t// test positive cases first\n\tfor step, tc := range tests {\n\t\tif tc.xfail != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tgot, err := scanPidLine(tc.input)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%d: unexpected error %v\", step, err)\n\t\t}\n\n\t\tif want := tc.pid; got != want {\n\t\t\tt.Errorf(\"%d: expected pid %d, got %d\", step, want, got)\n\t\t}\n\t}\n\n\t// test negative cases now\n\tfor step, tc := range tests {\n\t\tif tc.xfail == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t_, got := scanPidLine(tc.input)\n\t\tif want := tc.xfail; got != want {\n\t\t\tt.Errorf(\"%d: expected error %v, got %v\", step, want, got)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "lockfile_unix.go",
    "content": "// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris aix\n\npackage lockfile\n\nimport (\n\t\"os\"\n\t\"syscall\"\n)\n\nfunc isRunning(pid int) (bool, error) {\n\tproc, err := os.FindProcess(pid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif err := proc.Signal(syscall.Signal(0)); err != nil {\n\t\treturn false, nil\n\t}\n\n\treturn true, nil\n}\n"
  },
  {
    "path": "lockfile_windows.go",
    "content": "package lockfile\n\nimport (\n\t\"syscall\"\n)\n\n//For some reason these consts don't exist in syscall.\nconst (\n\terror_invalid_parameter = 87\n\tcode_still_active       = 259\n)\n\nfunc isRunning(pid int) (bool, error) {\n\tprocHnd, err := syscall.OpenProcess(syscall.PROCESS_QUERY_INFORMATION, true, uint32(pid))\n\tif err != nil {\n\t\tif scerr, ok := err.(syscall.Errno); ok {\n\t\t\tif uintptr(scerr) == error_invalid_parameter {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t}\n\n\tvar code uint32\n\terr = syscall.GetExitCodeProcess(procHnd, &code)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn code == code_still_active, nil\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  }
]