main pico / pkg / rsync / rsyncopts / rsyncopts.go
Antonio Mika  ·  2026-10-09
  1// Package rsyncopts parses the command line an rsync client sends to the
  2// server. It implements just enough of popt(3) for typical rsync(1)
  3// invocations, without aliases or option prefix matching (not --del, only
  4// --delete).
  5//
  6// If we encounter arguments that rsync(1) parses differently compared to this
  7// package, then this package should be adjusted to match rsync(1).
  8package rsyncopts
  9
 10import (
 11	"fmt"
 12	"math"
 13	"strconv"
 14	"strings"
 15	"syscall"
 16	"unicode"
 17)
 18
 19const (
 20	OPT_SERVER = 1000 + iota
 21	OPT_DAEMON
 22	OPT_SENDER
 23	OPT_EXCLUDE
 24	OPT_EXCLUDE_FROM
 25	OPT_FILTER
 26	OPT_COMPARE_DEST
 27	OPT_COPY_DEST
 28	OPT_LINK_DEST
 29	OPT_HELP
 30	OPT_INCLUDE
 31	OPT_INCLUDE_FROM
 32	OPT_MODIFY_WINDOW
 33	OPT_MIN_SIZE
 34	OPT_CHMOD
 35	OPT_READ_BATCH
 36	OPT_WRITE_BATCH
 37	OPT_ONLY_WRITE_BATCH
 38	OPT_MAX_SIZE
 39	OPT_NO_D
 40	OPT_APPEND
 41	OPT_NO_ICONV
 42	OPT_INFO
 43	OPT_DEBUG
 44	OPT_BLOCK_SIZE
 45	OPT_USERMAP
 46	OPT_GROUPMAP
 47	OPT_CHOWN
 48	OPT_BWLIMIT
 49	OPT_STDERR
 50	OPT_OLD_COMPRESS
 51	OPT_NEW_COMPRESS
 52	OPT_NO_COMPRESS
 53	OPT_OLD_ARGS
 54	OPT_STOP_AFTER
 55	OPT_STOP_AT
 56	OPT_REFUSED_BASE = 9000
 57)
 58
 59type infoLevel int
 60
 61const (
 62	INFO_BACKUP infoLevel = iota
 63	INFO_COPY
 64	INFO_DEL
 65	INFO_FLIST
 66	INFO_MISC
 67	INFO_MOUNT
 68	INFO_NAME
 69	INFO_NONREG
 70	INFO_PROGRESS
 71	INFO_REMOVE
 72	INFO_SKIP
 73	INFO_STATS
 74	INFO_SYMSAFE
 75	COUNT_INFO
 76)
 77
 78// NewOptions returns an Options struct with all options initialized to their
 79// default values. Note that ParseArguments will set some options (that default
 80// to -1) based on the encountered command-line flags and built-in rules.
 81func NewOptions() *Options {
 82	return &Options{
 83		msgs2stderr:          2, // Default: send errors to stderr for local & remote-shell transfers
 84		output_motd:          1,
 85		human_readable:       1,
 86		allow_inc_recurse:    1,
 87		xfer_dirs:            -1,
 88		relative_paths:       -1,
 89		implied_dirs:         1,
 90		max_delete:           math.MinInt32,
 91		max_size:             -1,
 92		min_size:             -1,
 93		whole_file:           -1,
 94		do_compression_level: math.MinInt32,
 95		rsync_path:           "rsync",
 96		default_af_hint:      syscall.AF_INET6,
 97		blocking_io:          -1,
 98	}
 99}
100
101type Options struct {
102	// not directly referenced in the table, but used in the special case code.
103	do_compression    int
104	info              [COUNT_INFO]uint16
105	version_opt_cnt   int
106	block_size        int64
107	max_size          int64
108	min_size          int64
109	batch_mode        int
110	basis_dirs        []string
111	modify_window_set int
112
113	// order matches long_options order
114	verbose                int
115	msgs2stderr            int
116	quiet                  int
117	output_motd            int
118	do_stats               int
119	human_readable         int
120	dry_run                int
121	recurse                int
122	allow_inc_recurse      int
123	xfer_dirs              int
124	preserve_perms         int
125	preserve_executability int
126	preserve_acls          int
127	preserve_xattrs        int
128	preserve_mtimes        int
129	preserve_atimes        int
130	open_noatime           int
131	preserve_crtimes       int
132	omit_dir_times         int
133	omit_link_times        int
134	modify_window          int
135	am_root                int // 0 = normal, 1 = root, 2 = --super, -1 = --fake-super
136	preserve_uid           int
137	preserve_gid           int
138	preserve_devices       int
139	copy_devices           int
140	write_devices          int
141	preserve_specials      int
142	preserve_links         int
143	copy_links             int
144	copy_unsafe_links      int
145	safe_symlinks          int
146	munge_symlinks         int
147	copy_dirlinks          int
148	keep_dirlinks          int
149	preserve_hard_links    int
150	relative_paths         int
151	implied_dirs           int
152	ignore_times           int
153	size_only              int
154	one_file_system        int
155	update_only            int
156	ignore_non_existing    int
157	ignore_existing        int
158	max_size_arg           string
159	min_size_arg           string
160	max_alloc_arg          string
161	sparse_files           int
162	preallocate_files      int
163	inplace                int
164	append_mode            int
165	delete_during          int
166	delete_mode            int
167	delete_before          int
168	delete_after           int
169	delete_excluded        int
170	missing_args           int // 0 = FERROR_XFER, 1 = ignore, 2 = delete
171	remove_source_files    int
172	force_delete           int
173	ignore_errors          int
174	max_delete             int
175	cvs_exclude            int
176	// If 1, send the whole file as literal data rather than trying to create an
177	// incremental diff.
178	// If -1, then look at whether we're local or remote and go by that.
179	// See also disable_deltas_p()
180	whole_file           int
181	always_checksum      int
182	checksum_choice      string
183	fuzzy_basis          int
184	compress_choice      string
185	skip_compress        string
186	do_compression_level int
187	do_progress          int
188	keep_partial         int
189	partial_dir          string
190	delay_updates        int
191	prune_empty_dirs     int
192	logfile_name         string
193	logfile_format       string
194	stdout_format        string
195	itemize_changes      int
196	bwlimit_arg          string
197	bwlimit              int
198	make_backups         int
199	backup_dir           string
200	backup_suffix        string
201	list_only            int
202	batch_name           string
203	files_from           string
204	eol_nulls            int
205	old_style_args       int // intentionally set to 0; unsupported
206	protect_args         int // intentionally set to 0; currently unsupported
207	trust_sender         int
208	numeric_ids          int
209	io_timeout           int
210	connect_timeout      int
211	do_fsync             int
212	shell_cmd            string
213	rsync_path           string
214	tmpdir               string
215	iconv_opt            string
216	default_af_hint      int
217	allow_8bit_chars     int
218	mkpath_dest_arg      int
219	use_qsort            int
220	copy_as              string
221	bind_address         string // numeric IPv4 or IPv6, or a hostname
222	rsync_port           int
223	sockopts             string
224	password_file        string
225	early_input_file     string
226	blocking_io          int
227	outbuf_mode          string
228	protocol_version     int
229	checksum_seed        int
230	am_server            int
231	am_sender            int
232}
233
234type priority int
235
236const (
237	DEFAULT_PRIORITY priority = iota
238	HELP_PRIORITY
239	USER_PRIORITY
240	LIMIT_PRIORITY
241)
242
243const (
244	W_CLI = 1 << iota
245	W_SRV
246	W_SND
247	W_REC
248)
249
250type output struct {
251	name  string
252	where int
253	help  string
254}
255
256var infoWords = [...]output{
257	{"BACKUP", W_REC, "Mention files backed up"},
258	{"COPY", W_REC, "Mention files copied locally on the receiving side"},
259	{"DEL", W_REC, "Mention deletions on the receiving side"},
260	{"FLIST", W_CLI, "Mention file-list receiving/sending (levels 1-2)"},
261	{"MISC", W_SND | W_REC, "Mention miscellaneous information (levels 1-2)"},
262	{"MOUNT", W_SND | W_REC, "Mention mounts that were found or skipped"},
263	{"NAME", W_SND | W_REC, "Mention 1) updated file/dir names, 2) unchanged names"},
264	{"NONREG", W_REC, "Mention skipped non-regular files (default 1, 0 disables)"},
265	{"PROGRESS", W_CLI, "Mention 1) per-file progress or 2) total transfer progress"},
266	{"REMOVE", W_SND, "Mention files removed on the sending side"},
267	{"SKIP", W_REC, "Mention files skipped due to transfer overrides (levels 1-2)"},
268	{"STATS", W_CLI | W_SRV, "Mention statistics at end of run (levels 1-3)"},
269	{"SYMSAFE", W_SND | W_REC, "Mention symlinks that are unsafe"},
270}
271
272func parseOutputWords(words []output, levels []uint16, str string, prio priority) {
273Level:
274	for s := range strings.SplitSeq(str, ",") {
275		if strings.TrimSpace(s) == "" {
276			continue
277		}
278		trimmed := strings.TrimRightFunc(s, unicode.IsNumber)
279		lev := 1
280		if len(trimmed) < len(s) {
281			var err error
282			lev, err = strconv.Atoi(s[len(trimmed):])
283			if err != nil {
284				continue
285			}
286		}
287		trimmed = strings.ToLower(trimmed)
288		all := false
289		switch trimmed {
290		case "none":
291			lev = 0
292		case "all":
293			all = true
294		}
295		for j := range words {
296			word := words[j]
297			if strings.ToLower(word.name) == trimmed || all {
298				levels[j] = uint16(lev)
299				if !all {
300					continue Level
301				}
302			}
303		}
304	}
305}
306
307func (o *Options) setOutputVerbosity(prio priority) {
308	// debugVerbosity is reserved for future use with debug logging levels.
309	// debugVerbosity := [...]string{
310	//     "",
311	//     "",
312	//     "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV",
313	//     "ACL,BACKUP,CONNECT2,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME",
314	//     "CMD2,DELTASUM3,DEL3,EXIT2,FLIST3,ICONV2,OWN2,PROTO,TIME2",
315	//     "CHDIR,DELTASUM4,FLIST4,FUZZY2,HASH,HLINK",
316	// }
317	infoVerbosity := [...]string{
318		"NONREG",
319		"COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE",
320		"BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP",
321	}
322	for j := 0; j <= o.verbose; j++ {
323		if j < len(infoVerbosity) {
324			parseOutputWords(infoWords[:], o.info[:], infoVerbosity[j], prio)
325		}
326		// TODO: enable debug verbosity when debug logging is implemented
327		// if j < len(debugVerbosity) {
328		//     parseOutputWords(debugWords[:], o.debug[:], debugVerbosity[j], prio)
329		// }
330	}
331}
332
333func (o *Options) table() []poptOption {
334	return []poptOption{
335		/* longName, shortName, argInfo, arg, val */
336		{"help", "", POPT_ARG_NONE, nil, OPT_HELP},
337		{"version", "V", POPT_ARG_NONE, nil, 'V'},
338		{"verbose", "v", POPT_ARG_NONE, nil, 'v'},
339		{"no-verbose", "", POPT_ARG_VAL, &o.verbose, 0},
340		{"no-v", "", POPT_ARG_VAL, &o.verbose, 0},
341		{"info", "", POPT_ARG_STRING, nil, OPT_INFO},
342		{"debug", "", POPT_ARG_STRING, nil, OPT_DEBUG},
343		{"stderr", "", POPT_ARG_STRING, nil, OPT_STDERR},
344		{"msgs2stderr", "", POPT_ARG_VAL, &o.msgs2stderr, 1},
345		{"no-msgs2stderr", "", POPT_ARG_VAL, &o.msgs2stderr, 0},
346		{"quiet", "q", POPT_ARG_NONE, nil, 'q'},
347		{"motd", "", POPT_ARG_VAL, &o.output_motd, 1},
348		{"no-motd", "", POPT_ARG_VAL, &o.output_motd, 0},
349		{"stats", "", POPT_ARG_NONE, &o.do_stats, 0},
350		{"human-readable", "h", POPT_ARG_NONE, nil, 'h'},
351		{"no-human-readable", "", POPT_ARG_VAL, &o.human_readable, 0},
352		{"no-h", "", POPT_ARG_VAL, &o.human_readable, 0},
353		{"dry-run", "n", POPT_ARG_NONE, &o.dry_run, 0},
354		{"archive", "a", POPT_ARG_NONE, nil, 'a'},
355		{"recursive", "r", POPT_ARG_VAL, &o.recurse, 2},
356		{"no-recursive", "", POPT_ARG_VAL, &o.recurse, 0},
357		{"no-r", "", POPT_ARG_VAL, &o.recurse, 0},
358		{"inc-recursive", "", POPT_ARG_VAL, &o.allow_inc_recurse, 1},
359		{"no-inc-recursive", "", POPT_ARG_VAL, &o.allow_inc_recurse, 0},
360		{"i-r", "", POPT_ARG_VAL, &o.allow_inc_recurse, 1},
361		{"no-i-r", "", POPT_ARG_VAL, &o.allow_inc_recurse, 0},
362		{"dirs", "d", POPT_ARG_VAL, &o.xfer_dirs, 2},
363		{"no-dirs", "", POPT_ARG_VAL, &o.xfer_dirs, 0},
364		{"no-d", "", POPT_ARG_VAL, &o.xfer_dirs, 0},
365		{"old-dirs", "", POPT_ARG_VAL, &o.xfer_dirs, 4},
366		{"old-d", "", POPT_ARG_VAL, &o.xfer_dirs, 4},
367		{"perms", "p", POPT_ARG_VAL, &o.preserve_perms, 1},
368		{"no-perms", "", POPT_ARG_VAL, &o.preserve_perms, 0},
369		{"no-p", "", POPT_ARG_VAL, &o.preserve_perms, 0},
370		{"executability", "E", POPT_ARG_NONE, &o.preserve_executability, 0},
371		{"acls", "A", POPT_ARG_NONE, nil, 'A'},
372		{"no-acls", "", POPT_ARG_VAL, &o.preserve_acls, 0},
373		{"no-A", "", POPT_ARG_VAL, &o.preserve_acls, 0},
374		{"xattrs", "X", POPT_ARG_NONE, nil, 'X'},
375		{"no-xattrs", "", POPT_ARG_VAL, &o.preserve_xattrs, 0},
376		{"no-X", "", POPT_ARG_VAL, &o.preserve_xattrs, 0},
377		{"times", "t", POPT_ARG_VAL, &o.preserve_mtimes, 1},
378		{"no-times", "", POPT_ARG_VAL, &o.preserve_mtimes, 0},
379		{"no-t", "", POPT_ARG_VAL, &o.preserve_mtimes, 0},
380		{"atimes", "U", POPT_ARG_NONE, nil, 'U'},
381		{"no-atimes", "", POPT_ARG_VAL, &o.preserve_atimes, 0},
382		{"no-U", "", POPT_ARG_VAL, &o.preserve_atimes, 0},
383		{"open-noatime", "", POPT_ARG_VAL, &o.open_noatime, 1},
384		{"no-open-noatime", "", POPT_ARG_VAL, &o.open_noatime, 0},
385		{"crtimes", "N", POPT_ARG_NONE, &o.preserve_crtimes, 1}, // refused
386		{"no-crtimes", "", POPT_ARG_VAL, &o.preserve_crtimes, 0},
387		{"no-N", "", POPT_ARG_VAL, &o.preserve_crtimes, 0},
388		{"omit-dir-times", "O", POPT_ARG_VAL, &o.omit_dir_times, 1},
389		{"no-omit-dir-times", "", POPT_ARG_VAL, &o.omit_dir_times, 0},
390		{"no-O", "", POPT_ARG_VAL, &o.omit_dir_times, 0},
391		{"omit-link-times", "J", POPT_ARG_VAL, &o.omit_link_times, 1},
392		{"no-omit-link-times", "", POPT_ARG_VAL, &o.omit_link_times, 0},
393		{"no-J", "", POPT_ARG_VAL, &o.omit_link_times, 0},
394		{"modify-window", "@", POPT_ARG_INT, &o.modify_window, OPT_MODIFY_WINDOW},
395		{"super", "", POPT_ARG_VAL, &o.am_root, 2},
396		{"no-super", "", POPT_ARG_VAL, &o.am_root, 0},
397		{"fake-super", "", POPT_ARG_VAL, &o.am_root, -1},
398		{"owner", "o", POPT_ARG_VAL, &o.preserve_uid, 1},
399		{"no-owner", "", POPT_ARG_VAL, &o.preserve_uid, 0},
400		{"no-o", "", POPT_ARG_VAL, &o.preserve_uid, 0},
401		{"group", "g", POPT_ARG_VAL, &o.preserve_gid, 1},
402		{"no-group", "", POPT_ARG_VAL, &o.preserve_gid, 0},
403		{"no-g", "", POPT_ARG_VAL, &o.preserve_gid, 0},
404		{"", "D", POPT_ARG_NONE, nil, 'D'},
405		{"no-D", "", POPT_ARG_NONE, nil, OPT_NO_D},
406		{"devices", "", POPT_ARG_VAL, &o.preserve_devices, 1},
407		{"no-devices", "", POPT_ARG_VAL, &o.preserve_devices, 0},
408		{"copy-devices", "", POPT_ARG_NONE, &o.copy_devices, 0},
409		{"write-devices", "", POPT_ARG_VAL, &o.write_devices, 1},
410		{"no-write-devices", "", POPT_ARG_VAL, &o.write_devices, 0},
411		{"specials", "", POPT_ARG_VAL, &o.preserve_specials, 1},
412		{"no-specials", "", POPT_ARG_VAL, &o.preserve_specials, 0},
413		{"links", "l", POPT_ARG_VAL, &o.preserve_links, 1},
414		{"no-links", "", POPT_ARG_VAL, &o.preserve_links, 0},
415		{"no-l", "", POPT_ARG_VAL, &o.preserve_links, 0},
416		{"copy-links", "L", POPT_ARG_NONE, &o.copy_links, 0},
417		{"copy-unsafe-links", "", POPT_ARG_NONE, &o.copy_unsafe_links, 0},
418		{"safe-links", "", POPT_ARG_NONE, &o.safe_symlinks, 0},
419		{"munge-links", "", POPT_ARG_VAL, &o.munge_symlinks, 1},
420		{"no-munge-links", "", POPT_ARG_VAL, &o.munge_symlinks, 0},
421		{"copy-dirlinks", "k", POPT_ARG_NONE, &o.copy_dirlinks, 0},
422		{"keep-dirlinks", "K", POPT_ARG_NONE, &o.keep_dirlinks, 0},
423		{"hard-links", "H", POPT_ARG_NONE, nil, 'H'},
424		{"no-hard-links", "", POPT_ARG_VAL, &o.preserve_hard_links, 0},
425		{"no-H", "", POPT_ARG_VAL, &o.preserve_hard_links, 0},
426		{"relative", "R", POPT_ARG_VAL, &o.relative_paths, 1},
427		{"no-relative", "", POPT_ARG_VAL, &o.relative_paths, 0},
428		{"no-R", "", POPT_ARG_VAL, &o.relative_paths, 0},
429		{"implied-dirs", "", POPT_ARG_VAL, &o.implied_dirs, 1},
430		{"no-implied-dirs", "", POPT_ARG_VAL, &o.implied_dirs, 0},
431		{"i-d", "", POPT_ARG_VAL, &o.implied_dirs, 1},
432		{"no-i-d", "", POPT_ARG_VAL, &o.implied_dirs, 0},
433		{"chmod", "", POPT_ARG_STRING, nil, OPT_CHMOD},
434		{"ignore-times", "I", POPT_ARG_NONE, &o.ignore_times, 0},
435		{"size-only", "", POPT_ARG_NONE, &o.size_only, 0},
436		{"one-file-system", "x", POPT_ARG_NONE, nil, 'x'},
437		{"no-one-file-system", "", POPT_ARG_VAL, &o.one_file_system, 0},
438		{"no-x", "", POPT_ARG_VAL, &o.one_file_system, 0},
439		{"update", "u", POPT_ARG_NONE, &o.update_only, 0},
440		{"existing", "", POPT_ARG_NONE, &o.ignore_non_existing, 0},
441		{"ignore-non-existing", "", POPT_ARG_NONE, &o.ignore_non_existing, 0},
442		{"ignore-existing", "", POPT_ARG_NONE, &o.ignore_existing, 0},
443		{"max-size", "", POPT_ARG_STRING, &o.max_size_arg, OPT_MAX_SIZE},
444		{"min-size", "", POPT_ARG_STRING, &o.min_size_arg, OPT_MIN_SIZE},
445		{"max-alloc", "", POPT_ARG_STRING, &o.max_alloc_arg, 0},
446		{"sparse", "S", POPT_ARG_VAL, &o.sparse_files, 1},
447		{"no-sparse", "", POPT_ARG_VAL, &o.sparse_files, 0},
448		{"no-S", "", POPT_ARG_VAL, &o.sparse_files, 0},
449		{"preallocate", "", POPT_ARG_NONE, &o.preallocate_files, 0},
450		{"inplace", "", POPT_ARG_VAL, &o.inplace, 1},
451		{"no-inplace", "", POPT_ARG_VAL, &o.inplace, 0},
452		{"append", "", POPT_ARG_NONE, nil, OPT_APPEND},
453		{"append-verify", "", POPT_ARG_VAL, &o.append_mode, 2},
454		{"no-append", "", POPT_ARG_VAL, &o.append_mode, 0},
455		{"del", "", POPT_ARG_NONE, &o.delete_during, 0},
456		{"delete", "", POPT_ARG_NONE, &o.delete_mode, 0},
457		{"delete-before", "", POPT_ARG_NONE, &o.delete_before, 0},
458		{"delete-during", "", POPT_ARG_VAL, &o.delete_during, 1},
459		{"delete-delay", "", POPT_ARG_VAL, &o.delete_during, 2},
460		{"delete-after", "", POPT_ARG_NONE, &o.delete_after, 0},
461		{"delete-excluded", "", POPT_ARG_NONE, &o.delete_excluded, 0},
462		{"delete-missing-args", "", POPT_BIT_SET, &o.missing_args, 2},
463		{"ignore-missing-args", "", POPT_BIT_SET, &o.missing_args, 1},
464		{"remove-sent-files", "", POPT_ARG_VAL, &o.remove_source_files, 2}, /* deprecated */
465		{"remove-source-files", "", POPT_ARG_VAL, &o.remove_source_files, 1},
466		{"force", "", POPT_ARG_VAL, &o.force_delete, 1},
467		{"no-force", "", POPT_ARG_VAL, &o.force_delete, 0},
468		{"ignore-errors", "", POPT_ARG_VAL, &o.ignore_errors, 1},
469		{"no-ignore-errors", "", POPT_ARG_VAL, &o.ignore_errors, 0},
470		{"max-delete", "", POPT_ARG_INT, &o.max_delete, 0},
471		{"", "F", POPT_ARG_NONE, nil, 'F'},
472		{"filter", "f", POPT_ARG_STRING, nil, OPT_FILTER},
473		{"exclude", "", POPT_ARG_STRING, nil, OPT_EXCLUDE},
474		{"include", "", POPT_ARG_STRING, nil, OPT_INCLUDE},
475		{"exclude-from", "", POPT_ARG_STRING, nil, OPT_EXCLUDE_FROM},
476		{"include-from", "", POPT_ARG_STRING, nil, OPT_INCLUDE_FROM},
477		{"cvs-exclude", "C", POPT_ARG_NONE, &o.cvs_exclude, 0},
478		{"whole-file", "W", POPT_ARG_VAL, &o.whole_file, 1},
479		{"no-whole-file", "", POPT_ARG_VAL, &o.whole_file, 0},
480		{"no-W", "", POPT_ARG_VAL, &o.whole_file, 0},
481		{"checksum", "c", POPT_ARG_VAL, &o.always_checksum, 1},
482		{"no-checksum", "", POPT_ARG_VAL, &o.always_checksum, 0},
483		{"no-c", "", POPT_ARG_VAL, &o.always_checksum, 0},
484		{"checksum-choice", "", POPT_ARG_STRING, &o.checksum_choice, 0},
485		{"cc", "", POPT_ARG_STRING, &o.checksum_choice, 0},
486		{"block-size", "B", POPT_ARG_STRING, nil, OPT_BLOCK_SIZE},
487		{"compare-dest", "", POPT_ARG_STRING, nil, OPT_COMPARE_DEST},
488		{"copy-dest", "", POPT_ARG_STRING, nil, OPT_COPY_DEST},
489		{"link-dest", "", POPT_ARG_STRING, nil, OPT_LINK_DEST},
490		{"fuzzy", "y", POPT_ARG_NONE, nil, 'y'},
491		{"no-fuzzy", "", POPT_ARG_VAL, &o.fuzzy_basis, 0},
492		{"no-y", "", POPT_ARG_VAL, &o.fuzzy_basis, 0},
493		{"compress", "z", POPT_ARG_NONE, nil, 'z'},
494		{"old-compress", "", POPT_ARG_NONE, nil, OPT_OLD_COMPRESS},
495		{"new-compress", "", POPT_ARG_NONE, nil, OPT_NEW_COMPRESS},
496		{"no-compress", "", POPT_ARG_NONE, nil, OPT_NO_COMPRESS},
497		{"no-z", "", POPT_ARG_NONE, nil, OPT_NO_COMPRESS},
498		{"compress-choice", "", POPT_ARG_STRING, &o.compress_choice, 0},
499		{"zc", "", POPT_ARG_STRING, &o.compress_choice, 0},
500		{"skip-compress", "", POPT_ARG_STRING, &o.skip_compress, 0},
501		{"compress-level", "", POPT_ARG_INT, &o.do_compression_level, 0},
502		{"zl", "", POPT_ARG_INT, &o.do_compression_level, 0},
503		{"", "P", POPT_ARG_NONE, nil, 'P'},
504		{"progress", "", POPT_ARG_VAL, &o.do_progress, 1},
505		{"no-progress", "", POPT_ARG_VAL, &o.do_progress, 0},
506		{"partial", "", POPT_ARG_VAL, &o.keep_partial, 1},
507		{"no-partial", "", POPT_ARG_VAL, &o.keep_partial, 0},
508		{"partial-dir", "", POPT_ARG_STRING, &o.partial_dir, 0},
509		{"delay-updates", "", POPT_ARG_VAL, &o.delay_updates, 1},
510		{"no-delay-updates", "", POPT_ARG_VAL, &o.delay_updates, 0},
511		{"prune-empty-dirs", "m", POPT_ARG_VAL, &o.prune_empty_dirs, 1},
512		{"no-prune-empty-dirs", "", POPT_ARG_VAL, &o.prune_empty_dirs, 0},
513		{"no-m", "", POPT_ARG_VAL, &o.prune_empty_dirs, 0},
514		{"log-file", "", POPT_ARG_STRING, &o.logfile_name, 0},
515		{"log-file-format", "", POPT_ARG_STRING, &o.logfile_format, 0},
516		{"out-format", "", POPT_ARG_STRING, &o.stdout_format, 0},
517		{"log-format", "", POPT_ARG_STRING, &o.stdout_format, 0}, /* DEPRECATED */
518		{"itemize-changes", "i", POPT_ARG_NONE, nil, 'i'},
519		{"no-itemize-changes", "", POPT_ARG_VAL, &o.itemize_changes, 0},
520		{"no-i", "", POPT_ARG_VAL, &o.itemize_changes, 0},
521		{"bwlimit", "", POPT_ARG_STRING, &o.bwlimit_arg, OPT_BWLIMIT},
522		{"no-bwlimit", "", POPT_ARG_VAL, &o.bwlimit, 0},
523		{"backup", "b", POPT_ARG_VAL, &o.make_backups, 1},
524		{"no-backup", "", POPT_ARG_VAL, &o.make_backups, 0},
525		{"backup-dir", "", POPT_ARG_STRING, &o.backup_dir, 0},
526		{"suffix", "", POPT_ARG_STRING, &o.backup_suffix, 0},
527		{"list-only", "", POPT_ARG_VAL, &o.list_only, 2},
528		{"read-batch", "", POPT_ARG_STRING, &o.batch_name, OPT_READ_BATCH},
529		{"write-batch", "", POPT_ARG_STRING, &o.batch_name, OPT_WRITE_BATCH},
530		{"only-write-batch", "", POPT_ARG_STRING, &o.batch_name, OPT_ONLY_WRITE_BATCH},
531		{"files-from", "", POPT_ARG_STRING, &o.files_from, 0},
532		{"from0", "0", POPT_ARG_VAL, &o.eol_nulls, 1},
533		{"no-from0", "", POPT_ARG_VAL, &o.eol_nulls, 0},
534		{"old-args", "", POPT_ARG_NONE, nil, OPT_OLD_ARGS},
535		{"no-old-args", "", POPT_ARG_VAL, &o.old_style_args, 0},
536		{"secluded-args", "s", POPT_ARG_VAL, &o.protect_args, 1},
537		{"no-secluded-args", "", POPT_ARG_VAL, &o.protect_args, 0},
538		{"protect-args", "", POPT_ARG_VAL, &o.protect_args, 1},
539		{"no-protect-args", "", POPT_ARG_VAL, &o.protect_args, 0},
540		{"no-s", "", POPT_ARG_VAL, &o.protect_args, 0},
541		{"trust-sender", "", POPT_ARG_VAL, &o.trust_sender, 1},
542		{"numeric-ids", "", POPT_ARG_VAL, &o.numeric_ids, 1},
543		{"no-numeric-ids", "", POPT_ARG_VAL, &o.numeric_ids, 0},
544		{"usermap", "", POPT_ARG_STRING, nil, OPT_USERMAP},
545		{"groupmap", "", POPT_ARG_STRING, nil, OPT_GROUPMAP},
546		{"chown", "", POPT_ARG_STRING, nil, OPT_CHOWN},
547		{"timeout", "", POPT_ARG_INT, &o.io_timeout, 0},
548		{"no-timeout", "", POPT_ARG_VAL, &o.io_timeout, 0},
549		{"contimeout", "", POPT_ARG_INT, &o.connect_timeout, 0},
550		{"no-contimeout", "", POPT_ARG_VAL, &o.connect_timeout, 0},
551		{"fsync", "", POPT_ARG_NONE, &o.do_fsync, 0},
552		{"stop-after", "", POPT_ARG_STRING, nil, OPT_STOP_AFTER},
553		{"time-limit", "", POPT_ARG_STRING, nil, OPT_STOP_AFTER}, /* earlier stop-after name */
554		{"stop-at", "", POPT_ARG_STRING, nil, OPT_STOP_AT},
555		{"rsh", "e", POPT_ARG_STRING, &o.shell_cmd, 0},
556		{"rsync-path", "", POPT_ARG_STRING, &o.rsync_path, 0},
557		{"temp-dir", "T", POPT_ARG_STRING, &o.tmpdir, 0},
558		{"iconv", "", POPT_ARG_STRING, &o.iconv_opt, 0},
559		{"no-iconv", "", POPT_ARG_NONE, nil, OPT_NO_ICONV},
560		{"ipv4", "4", POPT_ARG_VAL, &o.default_af_hint, syscall.AF_INET},
561		{"ipv6", "6", POPT_ARG_VAL, &o.default_af_hint, syscall.AF_INET6},
562		{"8-bit-output", "8", POPT_ARG_VAL, &o.allow_8bit_chars, 1},
563		{"no-8-bit-output", "", POPT_ARG_VAL, &o.allow_8bit_chars, 0},
564		{"no-8", "", POPT_ARG_VAL, &o.allow_8bit_chars, 0},
565		{"mkpath", "", POPT_ARG_VAL, &o.mkpath_dest_arg, 1},
566		{"no-mkpath", "", POPT_ARG_VAL, &o.mkpath_dest_arg, 0},
567		{"qsort", "", POPT_ARG_NONE, &o.use_qsort, 0},
568		{"use-qsort", "", POPT_ARG_NONE, &o.use_qsort, 0},
569		{"copy-as", "", POPT_ARG_STRING, &o.copy_as, 0},
570		{"address", "", POPT_ARG_STRING, &o.bind_address, 0},
571		{"port", "", POPT_ARG_INT, &o.rsync_port, 0},
572		{"sockopts", "", POPT_ARG_STRING, &o.sockopts, 0},
573		{"password-file", "", POPT_ARG_STRING, &o.password_file, 0},
574		{"early-input", "", POPT_ARG_STRING, &o.early_input_file, 0},
575		{"blocking-io", "", POPT_ARG_VAL, &o.blocking_io, 1},
576		{"no-blocking-io", "", POPT_ARG_VAL, &o.blocking_io, 0},
577		{"outbuf", "", POPT_ARG_STRING, &o.outbuf_mode, 0},
578		{"remote-option", "M", POPT_ARG_STRING, nil, 'M'},
579		{"protocol", "", POPT_ARG_INT, &o.protocol_version, 0},
580		{"checksum-seed", "", POPT_ARG_INT, &o.checksum_seed, 0},
581		{"server", "", POPT_ARG_NONE, nil, OPT_SERVER},
582		{"sender", "", POPT_ARG_NONE, nil, OPT_SENDER},
583		/* All the following options switch us into daemon-mode option-parsing. */
584		{"config", "", POPT_ARG_STRING, nil, OPT_DAEMON},
585		{"daemon", "", POPT_ARG_NONE, nil, OPT_DAEMON},
586		{"dparam", "", POPT_ARG_STRING, nil, OPT_DAEMON},
587		{"detach", "", POPT_ARG_NONE, nil, OPT_DAEMON},
588		{"no-detach", "", POPT_ARG_NONE, nil, OPT_DAEMON},
589	}
590}
591
592// ParseArguments parses the arguments that follow "rsync" on the command
593// line (rsync/options.c:parse_arguments).
594func ParseArguments(args []string) (*Context, error) {
595	opts := NewOptions()
596	pc := Context{
597		Options: opts,
598		table:   opts.table(),
599		args:    args,
600	}
601
602	for {
603		opt, err := pc.poptGetNextOpt()
604		if err != nil {
605			return nil, err
606		}
607		if opt == -1 {
608			break // done
609		}
610		// Most options are handled by poptGetNextOpt, only special cases
611		// are returned and handled here.
612		switch opt {
613		case 'V':
614			opts.version_opt_cnt++
615
616		case OPT_SERVER:
617			opts.am_server = 1
618
619		case OPT_SENDER:
620			if opts.am_server == 0 {
621				return nil, fmt.Errorf("--sender only allowed with --server")
622			}
623			opts.am_sender = 1
624
625		case OPT_DAEMON:
626			return nil, fmt.Errorf("daemon mode is not supported")
627
628		case OPT_FILTER, OPT_EXCLUDE, OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_EXCLUDE_FROM, 'F':
629			// Clients send their filter rules over the protocol instead.
630			return nil, fmt.Errorf("filter options are not accepted on the server side")
631
632		case 'a':
633			if opts.recurse == 0 {
634				opts.recurse = 1
635			}
636			opts.preserve_links = 1
637			opts.preserve_perms = 1
638			opts.preserve_mtimes = 1
639			opts.preserve_gid = 1
640			opts.preserve_uid = 1
641			opts.preserve_devices = 1
642			opts.preserve_specials = 1
643
644		case 'D':
645			opts.preserve_devices = 1
646			opts.preserve_specials = 1
647
648		case OPT_NO_D:
649			opts.preserve_devices = 0
650			opts.preserve_specials = 0
651
652		case 'h':
653			opts.human_readable++
654
655		case 'H':
656			opts.preserve_hard_links++
657
658		case 'i':
659			opts.itemize_changes++
660
661		case 'U':
662			opts.preserve_atimes++
663			if opts.preserve_atimes > 1 {
664				opts.open_noatime = 1
665			}
666
667		case 'v':
668			opts.verbose++
669
670		case 'y':
671			opts.fuzzy_basis++
672
673		case 'q':
674			opts.quiet++
675
676		case 'x':
677			opts.one_file_system++
678
679		case 'P':
680			opts.do_progress = 1
681			opts.keep_partial = 1
682
683		case 'z':
684			opts.do_compression++
685
686		case OPT_OLD_COMPRESS:
687			opts.compress_choice = "zlib"
688
689		case OPT_NEW_COMPRESS:
690			opts.compress_choice = "zlibx"
691
692		case OPT_NO_COMPRESS:
693			opts.do_compression = 0
694			opts.compress_choice = ""
695
696		case OPT_OLD_ARGS:
697			opts.old_style_args++
698
699		case 'M':
700			return nil, fmt.Errorf("--remote-option is not accepted on the server side")
701
702		case OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_READ_BATCH:
703			opts.batch_mode = 1
704
705		case OPT_BLOCK_SIZE:
706			size, err := parseSizeArg(pc.poptGetOptArg(), 'b')
707			if err != nil || size < 0 {
708				return nil, fmt.Errorf("invalid --block-size value")
709			}
710			opts.block_size = size
711
712		case OPT_MAX_SIZE:
713			size, err := parseSizeArg(opts.max_size_arg, 'b')
714			if err != nil {
715				return nil, fmt.Errorf("invalid --max-size value: %s", opts.max_size_arg)
716			}
717			opts.max_size = size
718
719		case OPT_MIN_SIZE:
720			size, err := parseSizeArg(opts.min_size_arg, 'b')
721			if err != nil {
722				return nil, fmt.Errorf("invalid --min-size value: %s", opts.min_size_arg)
723			}
724			opts.min_size = size
725
726		case OPT_APPEND:
727			opts.append_mode++
728
729		case OPT_LINK_DEST, OPT_COPY_DEST, OPT_COMPARE_DEST:
730			opts.basis_dirs = append(opts.basis_dirs, pc.poptGetOptArg())
731
732		case OPT_INFO:
733			parseOutputWords(infoWords[:], opts.info[:], pc.poptGetOptArg(), USER_PRIORITY)
734
735		case 'A':
736			opts.preserve_acls++
737
738		case 'X':
739			opts.preserve_xattrs++
740
741		case OPT_MODIFY_WINDOW:
742			opts.modify_window_set = 1
743
744		case OPT_DEBUG, OPT_CHMOD, OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN,
745			OPT_BWLIMIT, OPT_STOP_AFTER, OPT_STOP_AT, OPT_STDERR, OPT_NO_ICONV:
746			// These don't change what the server does.
747			_ = pc.poptGetOptArg()
748
749		default:
750			return nil, fmt.Errorf("unhandled special case opt: %v", opt)
751		}
752	}
753
754	// rsync/options.c line 1973 and following set option defaults based on
755	// other options
756
757	opts.setOutputVerbosity(DEFAULT_PRIORITY)
758
759	if opts.delete_before+min(opts.delete_during, 1)+opts.delete_after > 1 {
760		return nil, fmt.Errorf("you may not combine multiple --delete-WHEN options")
761	}
762	if opts.delete_before != 0 || opts.delete_during != 0 || opts.delete_after != 0 {
763		opts.delete_mode = 1
764	} else if opts.delete_mode != 0 || opts.delete_excluded != 0 {
765		opts.delete_mode = 1
766	}
767
768	if opts.recurse != 0 {
769		opts.xfer_dirs = 1
770	}
771	if opts.xfer_dirs < 0 {
772		if opts.list_only != 0 {
773			opts.xfer_dirs = 1
774		} else {
775			opts.xfer_dirs = 0
776		}
777	}
778
779	if opts.relative_paths < 0 {
780		if opts.files_from != "" {
781			opts.relative_paths = 1
782		} else {
783			opts.relative_paths = 0
784		}
785	}
786
787	if opts.relative_paths == 0 {
788		opts.implied_dirs = 0
789	}
790
791	// NOTE: This simplification means that even if we ignore POPT_ARGFLAG_OR
792	// and store ints without regards for bit sets, we get the same result.
793	// Nevertheless, we support bit to be future-proof as new options are added.
794	if opts.missing_args == 3 {
795		// simplify if both options were specified
796		opts.missing_args = 2
797	}
798
799	if opts.backup_suffix == "" && opts.backup_dir == "" {
800		opts.backup_suffix = "~"
801	}
802
803	if opts.backup_dir != "" {
804		opts.make_backups = 1 // --backup-dir implies --backup
805	}
806
807	if opts.do_progress != 0 {
808		if opts.info[INFO_NAME] == 0 {
809			opts.info[INFO_NAME] = 1
810		}
811	}
812
813	if opts.info[INFO_NAME] >= 1 && opts.stdout_format == "" {
814		opts.stdout_format = "%n%L"
815	}
816
817	return &pc, nil
818}
819
820// parseSizeArg parses a size like 100K or 1.5MB (rsync/options.c
821// parse_size_arg). A bare number is in units of defSuffix.
822func parseSizeArg(arg string, defSuffix byte) (int64, error) {
823	s := arg
824	i := 0
825	for i < len(s) && (s[i] >= '0' && s[i] <= '9' || s[i] == '.') {
826		i++
827	}
828	if i == 0 {
829		return 0, fmt.Errorf("invalid size %q", arg)
830	}
831	num, err := strconv.ParseFloat(s[:i], 64)
832	if err != nil {
833		return 0, err
834	}
835	s = s[i:]
836	suffix := defSuffix
837	if s != "" {
838		suffix = s[0]
839		s = s[1:]
840	}
841	mult := float64(1024)
842	switch {
843	case strings.HasPrefix(strings.ToLower(s), "ib"):
844		s = s[2:]
845	case strings.HasPrefix(strings.ToLower(s), "b"):
846		mult = 1000
847		s = s[1:]
848	}
849	var size float64
850	switch suffix | 0x20 {
851	case 'b':
852		size = num
853	case 'k':
854		size = num * mult
855	case 'm':
856		size = num * mult * mult
857	case 'g':
858		size = num * mult * mult * mult
859	case 't':
860		size = num * mult * mult * mult * mult
861	case 'p':
862		size = num * mult * mult * mult * mult * mult
863	default:
864		return 0, fmt.Errorf("invalid size %q", arg)
865	}
866	switch s {
867	case "":
868	case "+1":
869		size++
870	case "-1":
871		size--
872	default:
873		return 0, fmt.Errorf("invalid size %q", arg)
874	}
875	return int64(size), nil
876}
877
878func (o *Options) Verbose() int             { return o.verbose }
879func (o *Options) Info(level infoLevel) int { return int(o.info[level]) }
880func (o *Options) DryRun() bool             { return o.dry_run != 0 }
881func (o *Options) Server() bool             { return o.am_server != 0 }
882func (o *Options) Sender() bool             { return o.am_sender != 0 }
883func (o *Options) Recurse() bool            { return o.recurse != 0 }
884func (o *Options) XferDirs() bool           { return o.xfer_dirs != 0 }
885func (o *Options) ListOnly() bool           { return o.list_only != 0 }
886func (o *Options) RelativePaths() bool      { return o.relative_paths != 0 }
887func (o *Options) PreserveLinks() bool      { return o.preserve_links != 0 }
888func (o *Options) PreserveHardLinks() bool  { return o.preserve_hard_links != 0 }
889func (o *Options) PreserveUid() bool        { return o.preserve_uid != 0 }
890func (o *Options) PreserveGid() bool        { return o.preserve_gid != 0 }
891func (o *Options) PreserveDevices() bool    { return o.preserve_devices != 0 }
892func (o *Options) PreserveSpecials() bool   { return o.preserve_specials != 0 }
893func (o *Options) PreserveMTimes() bool     { return o.preserve_mtimes != 0 }
894func (o *Options) PreservePerms() bool      { return o.preserve_perms != 0 }
895func (o *Options) PreserveACLs() bool       { return o.preserve_acls != 0 }
896func (o *Options) PreserveXattrs() bool     { return o.preserve_xattrs != 0 }
897func (o *Options) PreserveAtimes() bool     { return o.preserve_atimes != 0 }
898func (o *Options) PreserveCrtimes() bool    { return o.preserve_crtimes != 0 }
899func (o *Options) NumericIDs() bool         { return o.numeric_ids != 0 }
900func (o *Options) DeleteMode() bool         { return o.delete_mode != 0 }
901func (o *Options) DeleteBefore() bool       { return o.delete_before != 0 }
902func (o *Options) DeleteDuring() int        { return o.delete_during }
903func (o *Options) DeleteAfter() bool        { return o.delete_after != 0 }
904func (o *Options) DeleteExcluded() bool     { return o.delete_excluded != 0 }
905func (o *Options) ForceDelete() bool        { return o.force_delete != 0 }
906func (o *Options) MaxDelete() int           { return o.max_delete }
907func (o *Options) IgnoreErrors() bool       { return o.ignore_errors != 0 }
908func (o *Options) PruneEmptyDirs() bool     { return o.prune_empty_dirs != 0 }
909func (o *Options) AlwaysChecksum() bool     { return o.always_checksum != 0 }
910func (o *Options) ChecksumChoice() string   { return o.checksum_choice }
911func (o *Options) ChecksumSeed() int        { return o.checksum_seed }
912func (o *Options) Compress() bool           { return o.do_compression != 0 }
913func (o *Options) CompressChoice() string   { return o.compress_choice }
914func (o *Options) CompressLevel() int       { return o.do_compression_level }
915func (o *Options) IgnoreTimes() bool        { return o.ignore_times != 0 }
916func (o *Options) SizeOnly() bool           { return o.size_only != 0 }
917func (o *Options) UpdateOnly() bool         { return o.update_only != 0 }
918func (o *Options) IgnoreExisting() bool     { return o.ignore_existing != 0 }
919func (o *Options) IgnoreNonExisting() bool  { return o.ignore_non_existing != 0 }
920func (o *Options) WholeFile() bool          { return o.whole_file > 0 }
921func (o *Options) BlockSize() int64         { return o.block_size }
922func (o *Options) MaxSize() int64           { return o.max_size }
923func (o *Options) MinSize() int64           { return o.min_size }
924func (o *Options) Inplace() bool            { return o.inplace != 0 }
925func (o *Options) AppendMode() int          { return o.append_mode }
926func (o *Options) DelayUpdates() bool       { return o.delay_updates != 0 }
927func (o *Options) RemoveSourceFiles() int   { return o.remove_source_files }
928func (o *Options) CvsExclude() bool         { return o.cvs_exclude != 0 }
929func (o *Options) ProtocolVersion() int     { return o.protocol_version }
930func (o *Options) ProtectArgs() bool        { return o.protect_args != 0 }
931func (o *Options) ShellCmd() string         { return o.shell_cmd }
932func (o *Options) FilesFrom() string        { return o.files_from }
933func (o *Options) MissingArgs() int         { return o.missing_args }
934func (o *Options) BatchMode() bool          { return o.batch_mode != 0 }
935func (o *Options) BasisDirs() []string      { return o.basis_dirs }
936func (o *Options) Iconv() string            { return o.iconv_opt }
937func (o *Options) ModifyWindowSet() bool    { return o.modify_window_set != 0 }