├── .gitignore ├── Makefile ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── ddcp.install ├── rules └── source │ └── format ├── main.go └── src └── ddcp ├── ddcp.go └── optparse.go /.gitignore: -------------------------------------------------------------------------------- 1 | /ddcp 2 | /*.gz 3 | /debian/ddcp.debhelper.log 4 | /debian/ddcp.substvars 5 | /debian/ddcp/ 6 | /debian/files 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX=/usr/local 2 | VERSION=`git tag | tail -n 1` 3 | GOOS=`go env GOOS` 4 | GOARCH=`go env GOARCH` 5 | 6 | ifdef GOPATH 7 | RUNTIME_GOPATH=$(GOPATH):`pwd` 8 | else 9 | RUNTIME_GOPATH=`pwd` 10 | endif 11 | 12 | ddcp: main.go src/ddcp/optparse.go src/ddcp/ddcp.go 13 | GOPATH=$(RUNTIME_GOPATH) go build -o ddcp main.go 14 | 15 | install: ddcp 16 | install -m 755 ddcp $(DESTDIR)$(PREFIX)/bin/ 17 | 18 | clean: 19 | rm -f ddcp *.gz 20 | 21 | package: clean ddcp 22 | gzip -c ddcp > ddcp-$(VERSION)-$(GOOS)-$(GOARCH).gz 23 | 24 | deb: 25 | dpkg-buildpackage -us -uc 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddcp 2 | 3 | Parallel file copy command using [dd](https://en.wikipedia.org/wiki/Dd_%28Unix%29). 4 | 5 | ## Usage 6 | 7 | ``` 8 | Usage of ddcp: 9 | -d string 10 | dest 11 | -n int 12 | chunk size [mb] (default 100) 13 | -p preserve attributes 14 | -s string 15 | source 16 | ``` 17 | 18 | ```sh 19 | ddcp -s source_file.dat -d dest_file.dat 20 | ``` 21 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | ddcp (0.1.2) unstable; urgency=low 2 | 3 | * Initial Release. 4 | 5 | -- Genki Sugawara Thu, 10 Mar 2016 15:16:47 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: ddcp 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Genki Sugawara 5 | Build-Depends: debhelper (>= 8.0.0) 6 | Standards-Version: 3.9.4 7 | Homepage: https://github.com/winebarrel/ddcp 8 | #Vcs-Git: git://git.debian.org/collab-maint/ddcp.git 9 | #Vcs-Browser: http://git.debian.org/?p=collab-maint/ddcp.git;a=summary 10 | 11 | Package: ddcp 12 | Architecture: any 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: Parallel file copy command using dd 15 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ddcp 3 | Source: https://github.com/winebarrel/ddcp 4 | 5 | Files: * 6 | Copyright: 2016 Genki Sugawara 7 | License: MIT 8 | 9 | Files: debian/* 10 | Copyright: Genki Sugawara 11 | License: MIT 12 | 13 | License: MIT 14 | Permission is hereby granted, free of charge, to any person obtaining a 15 | copy of this software and associated documentation files (the "Software"), 16 | to deal in the Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 18 | and/or sell copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following conditions: 20 | . 21 | The above copyright notice and this permission notice shall be included 22 | in all copies or substantial portions of the Software. 23 | . 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 25 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 28 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 29 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 30 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | # Please also look if there are files or directories which have a 33 | # different copyright/license attached and list them here. 34 | # Please avoid to pick license terms that are more restrictive than the 35 | # packaged work, as it may make Debian's contributions unacceptable upstream. 36 | -------------------------------------------------------------------------------- /debian/ddcp.install: -------------------------------------------------------------------------------- 1 | ddcp usr/bin 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | %: 8 | dh $@ 9 | 10 | override_dh_auto_install: 11 | # nothing to do 12 | 13 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ddcp" 5 | "log" 6 | "os" 7 | "runtime" 8 | ) 9 | 10 | func init() { 11 | log.SetFlags(0) 12 | } 13 | 14 | func main() { 15 | defer func() { 16 | if err := recover(); err != nil { 17 | log.Fatal(err) 18 | } 19 | }() 20 | 21 | max_procs := os.Getenv("GOMAXPROCS") 22 | 23 | if max_procs == "" { 24 | cpus := runtime.NumCPU() 25 | runtime.GOMAXPROCS(cpus) 26 | } 27 | 28 | params := ddcp.ParseFlag() 29 | err := ddcp.Ddcp(params) 30 | 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ddcp/ddcp.go: -------------------------------------------------------------------------------- 1 | package ddcp 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strconv" 8 | "syscall" 9 | ) 10 | 11 | func ddOpes(opes map[string]string) (cmd_opes []string) { 12 | cmd_opes = []string{} 13 | 14 | for k, v := range opes { 15 | cmd_opes = append(cmd_opes, fmt.Sprintf("%s=%s", k, v)) 16 | } 17 | 18 | return 19 | } 20 | 21 | func ddOpesList(source string, dest string, chunk_size int64, chunk_num int64, remainder int64) (opes_list [][]string) { 22 | if remainder > 0 { 23 | chunk_num++ 24 | } 25 | 26 | opes_list = make([][]string, chunk_num) 27 | chunk_size_mb := chunk_size / (1024 * 1024) 28 | 29 | for i := int64(0); i < chunk_num; i++ { 30 | opes := map[string]string{ 31 | "if": source, 32 | "of": dest, 33 | "conv": "notrunc", 34 | "bs": "1047552"} 35 | 36 | if i < chunk_num-1 { 37 | opes["count"] = strconv.FormatInt(chunk_size_mb, 10) 38 | } 39 | 40 | if i > 0 { 41 | offset := strconv.FormatInt(chunk_size_mb*i, 10) 42 | opes["skip"] = offset 43 | opes["seek"] = offset 44 | } 45 | 46 | opes_list[i] = ddOpes(opes) 47 | } 48 | 49 | return 50 | } 51 | 52 | func dd(opes []string, ch chan error) { 53 | out, err := exec.Command("dd", opes...).CombinedOutput() 54 | 55 | if err != nil { 56 | err = fmt.Errorf("'dd %s' is failed: %s", opes, out) 57 | } 58 | 59 | ch <- err 60 | } 61 | 62 | func runCmds(opes_list [][]string) (err error) { 63 | ch := make(chan error) 64 | 65 | for _, opes := range opes_list { 66 | go dd(opes, ch) 67 | } 68 | 69 | for _ = range opes_list { 70 | err = <-ch 71 | 72 | if err != nil { 73 | return 74 | } 75 | } 76 | 77 | return 78 | } 79 | 80 | func Ddcp(params *DdcpParams) (err error) { 81 | src, src_err := os.Stat(params.source) 82 | 83 | if src_err != nil { 84 | err = fmt.Errorf("source file does not exist: %s", params.source) 85 | return 86 | } 87 | 88 | src_size := src.Size() 89 | 90 | _, dst_err := os.Stat(params.dest) 91 | 92 | if dst_err == nil { 93 | err = fmt.Errorf("dest file already exists: %s", params.dest) 94 | return 95 | } 96 | 97 | if src_size == 0 { 98 | out, cp_err := exec.Command("cp", params.source, params.dest).CombinedOutput() 99 | 100 | if cp_err != nil { 101 | err = fmt.Errorf("'cp %s %s' is failed: %s", params.source, params.dest, out) 102 | return 103 | } 104 | } else { 105 | remainder := src_size % params.chunk_size 106 | chunk_num := src_size / params.chunk_size 107 | 108 | opes_list := ddOpesList(params.source, params.dest, params.chunk_size, chunk_num, remainder) 109 | err = runCmds(opes_list) 110 | 111 | if err != nil { 112 | return 113 | } 114 | } 115 | 116 | if params.preserve { 117 | err = os.Chmod(params.dest, src.Mode()) 118 | 119 | if err != nil { 120 | return 121 | } 122 | 123 | uid := src.Sys().(*syscall.Stat_t).Uid 124 | gid := src.Sys().(*syscall.Stat_t).Gid 125 | err = os.Chown(params.dest, int(uid), int(gid)) 126 | 127 | if err != nil { 128 | return 129 | } 130 | } 131 | 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /src/ddcp/optparse.go: -------------------------------------------------------------------------------- 1 | package ddcp 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | ) 7 | 8 | const ( 9 | DEFAULT_CHUNK_SIZE = 100 10 | ) 11 | 12 | type DdcpParams struct { 13 | source string 14 | dest string 15 | preserve bool 16 | chunk_size int64 17 | } 18 | 19 | func ParseFlag() (params *DdcpParams) { 20 | params = &DdcpParams{} 21 | 22 | flag.StringVar(¶ms.source, "s", "", "source") 23 | flag.StringVar(¶ms.dest, "d", "", "dest") 24 | flag.BoolVar(¶ms.preserve, "p", false, "preserve attributes") 25 | flag.Int64Var(¶ms.chunk_size, "n", DEFAULT_CHUNK_SIZE, "chunk size [mb]") 26 | flag.Parse() 27 | 28 | if params.source == "" { 29 | log.Fatal("'-s' is required") 30 | } 31 | 32 | if params.dest == "" { 33 | log.Fatal("'-d' is required") 34 | } 35 | 36 | params.chunk_size = params.chunk_size * 1024 * 1024 37 | 38 | return 39 | } 40 | --------------------------------------------------------------------------------