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 }