1"""
2Minion side functions for salt-cp
3"""
4
5import base64
6import errno
7import fnmatch
8import logging
9import os
10import urllib.parse
11
12import salt.crypt
13import salt.fileclient
14import salt.minion
15import salt.transport.client
16import salt.utils.data
17import salt.utils.files
18import salt.utils.gzip_util
19import salt.utils.path
20import salt.utils.templates
21import salt.utils.url
22from salt.exceptions import CommandExecutionError
23
24log = logging.getLogger(__name__)
25
26__proxyenabled__ = ["*"]
27
28
29def _auth():
30    """
31    Return the auth object
32    """
33    if "auth" not in __context__:
34        __context__["auth"] = salt.crypt.SAuth(__opts__)
35    return __context__["auth"]
36
37
38def _gather_pillar(pillarenv, pillar_override):
39    """
40    Whenever a state run starts, gather the pillar data fresh
41    """
42    pillar = salt.pillar.get_pillar(
43        __opts__,
44        __grains__,
45        __opts__["id"],
46        __opts__["saltenv"],
47        pillar_override=pillar_override,
48        pillarenv=pillarenv,
49    )
50    ret = pillar.compile_pillar()
51    if pillar_override and isinstance(pillar_override, dict):
52        ret.update(pillar_override)
53    return ret
54
55
56def recv(files, dest):
57    """
58    Used with salt-cp, pass the files dict, and the destination.
59
60    This function receives small fast copy files from the master via salt-cp.
61    It does not work via the CLI.
62    """
63    ret = {}
64    for path, data in files.items():
65        if os.path.basename(path) == os.path.basename(dest) and not os.path.isdir(dest):
66            final = dest
67        elif os.path.isdir(dest):
68            final = os.path.join(dest, os.path.basename(path))
69        elif os.path.isdir(os.path.dirname(dest)):
70            final = dest
71        else:
72            return "Destination unavailable"
73
74        try:
75            with salt.utils.files.fopen(final, "w+") as fp_:
76                fp_.write(data)
77            ret[final] = True
78        except OSError:
79            ret[final] = False
80
81    return ret
82
83
84def recv_chunked(dest, chunk, append=False, compressed=True, mode=None):
85    """
86    This function receives files copied to the minion using ``salt-cp`` and is
87    not intended to be used directly on the CLI.
88    """
89    if "retcode" not in __context__:
90        __context__["retcode"] = 0
91
92    def _error(msg):
93        __context__["retcode"] = 1
94        return msg
95
96    if chunk is None:
97        # dest is an empty dir and needs to be created
98        try:
99            os.makedirs(dest)
100        except OSError as exc:
101            if exc.errno == errno.EEXIST:
102                if os.path.isfile(dest):
103                    return "Path exists and is a file"
104            else:
105                return _error(exc.__str__())
106        return True
107
108    chunk = base64.b64decode(chunk)
109
110    open_mode = "ab" if append else "wb"
111    try:
112        fh_ = salt.utils.files.fopen(dest, open_mode)  # pylint: disable=W8470
113    except OSError as exc:
114        if exc.errno != errno.ENOENT:
115            # Parent dir does not exist, we need to create it
116            return _error(exc.__str__())
117        try:
118            os.makedirs(os.path.dirname(dest))
119        except OSError as makedirs_exc:
120            # Failed to make directory
121            return _error(makedirs_exc.__str__())
122        fh_ = salt.utils.files.fopen(dest, open_mode)  # pylint: disable=W8470
123
124    try:
125        # Write the chunk to disk
126        fh_.write(salt.utils.gzip_util.uncompress(chunk) if compressed else chunk)
127    except OSError as exc:
128        # Write failed
129        return _error(exc.__str__())
130    else:
131        # Write successful
132        if not append and mode is not None:
133            # If this is the first chunk we're writing, set the mode
134            # log.debug('Setting mode for %s to %s', dest, oct(mode))
135            log.debug("Setting mode for %s to %s", dest, mode)
136            try:
137                os.chmod(dest, mode)
138            except OSError:
139                return _error(exc.__str__())
140        return True
141    finally:
142        try:
143            fh_.close()
144        except AttributeError:
145            pass
146
147
148def _mk_client():
149    """
150    Create a file client and add it to the context.
151
152    Each file client needs to correspond to a unique copy
153    of the opts dictionary, therefore it's hashed by the
154    id of the __opts__ dict
155    """
156    if "cp.fileclient_{}".format(id(__opts__)) not in __context__:
157        __context__[
158            "cp.fileclient_{}".format(id(__opts__))
159        ] = salt.fileclient.get_file_client(__opts__)
160
161
162def _client():
163    """
164    Return a client, hashed by the list of masters
165    """
166    _mk_client()
167    return __context__["cp.fileclient_{}".format(id(__opts__))]
168
169
170def _render_filenames(path, dest, saltenv, template, **kw):
171    """
172    Process markup in the :param:`path` and :param:`dest` variables (NOT the
173    files under the paths they ultimately point to) according to the markup
174    format provided by :param:`template`.
175    """
176    if not template:
177        return (path, dest)
178
179    # render the path as a template using path_template_engine as the engine
180    if template not in salt.utils.templates.TEMPLATE_REGISTRY:
181        raise CommandExecutionError(
182            "Attempted to render file paths with unavailable engine {}".format(template)
183        )
184
185    kwargs = {}
186    kwargs["salt"] = __salt__
187    if "pillarenv" in kw or "pillar" in kw:
188        pillarenv = kw.get("pillarenv", __opts__.get("pillarenv"))
189        kwargs["pillar"] = _gather_pillar(pillarenv, kw.get("pillar"))
190    else:
191        kwargs["pillar"] = __pillar__
192    kwargs["grains"] = __grains__
193    kwargs["opts"] = __opts__
194    kwargs["saltenv"] = saltenv
195
196    def _render(contents):
197        """
198        Render :param:`contents` into a literal pathname by writing it to a
199        temp file, rendering that file, and returning the result.
200        """
201        # write out path to temp file
202        tmp_path_fn = salt.utils.files.mkstemp()
203        with salt.utils.files.fopen(tmp_path_fn, "w+") as fp_:
204            fp_.write(salt.utils.stringutils.to_str(contents))
205        data = salt.utils.templates.TEMPLATE_REGISTRY[template](
206            tmp_path_fn, to_str=True, **kwargs
207        )
208        salt.utils.files.safe_rm(tmp_path_fn)
209        if not data["result"]:
210            # Failed to render the template
211            raise CommandExecutionError(
212                "Failed to render file path with error: {}".format(data["data"])
213            )
214        else:
215            return data["data"]
216
217    path = _render(path)
218    dest = _render(dest)
219    return (path, dest)
220
221
222def get_file(
223    path, dest, saltenv="base", makedirs=False, template=None, gzip=None, **kwargs
224):
225    """
226    .. versionchanged:: 2018.3.0
227        ``dest`` can now be a directory
228
229    Used to get a single file from the salt master
230
231    CLI Example:
232
233    .. code-block:: bash
234
235        salt '*' cp.get_file salt://path/to/file /minion/dest
236
237    Template rendering can be enabled on both the source and destination file
238    names like so:
239
240    .. code-block:: bash
241
242        salt '*' cp.get_file "salt://{{grains.os}}/vimrc" /etc/vimrc template=jinja
243
244    This example would instruct all Salt minions to download the vimrc from a
245    directory with the same name as their os grain and copy it to /etc/vimrc
246
247    For larger files, the cp.get_file module also supports gzip compression.
248    Because gzip is CPU-intensive, this should only be used in scenarios where
249    the compression ratio is very high (e.g. pretty-printed JSON or YAML
250    files).
251
252    Use the *gzip* named argument to enable it.  Valid values are 1..9, where 1
253    is the lightest compression and 9 the heaviest.  1 uses the least CPU on
254    the master (and minion), 9 uses the most.
255
256    There are two ways of defining the fileserver environment (a.k.a.
257    ``saltenv``) from which to retrieve the file. One is to use the ``saltenv``
258    parameter, and the other is to use a querystring syntax in the ``salt://``
259    URL. The below two examples are equivalent:
260
261    .. code-block:: bash
262
263        salt '*' cp.get_file salt://foo/bar.conf /etc/foo/bar.conf saltenv=config
264        salt '*' cp.get_file salt://foo/bar.conf?saltenv=config /etc/foo/bar.conf
265
266    .. note::
267        It may be necessary to quote the URL when using the querystring method,
268        depending on the shell being used to run the command.
269    """
270    (path, dest) = _render_filenames(path, dest, saltenv, template, **kwargs)
271
272    path, senv = salt.utils.url.split_env(path)
273    if senv:
274        saltenv = senv
275
276    if not hash_file(path, saltenv):
277        return ""
278    else:
279        return _client().get_file(path, dest, makedirs, saltenv, gzip)
280
281
282def envs():
283    """
284    List available environments for fileserver
285
286    CLI Example
287
288    .. code-block:: bash
289
290        salt '*' cp.envs
291    """
292    return _client().envs()
293
294
295def get_template(
296    path, dest, template="jinja", saltenv="base", makedirs=False, **kwargs
297):
298    """
299    Render a file as a template before setting it down.
300    Warning, order is not the same as in fileclient.cp for
301    non breaking old API.
302
303    CLI Example:
304
305    .. code-block:: bash
306
307        salt '*' cp.get_template salt://path/to/template /minion/dest
308    """
309    if "salt" not in kwargs:
310        kwargs["salt"] = __salt__
311    if "pillar" not in kwargs:
312        kwargs["pillar"] = __pillar__
313    if "grains" not in kwargs:
314        kwargs["grains"] = __grains__
315    if "opts" not in kwargs:
316        kwargs["opts"] = __opts__
317    return _client().get_template(path, dest, template, makedirs, saltenv, **kwargs)
318
319
320def get_dir(path, dest, saltenv="base", template=None, gzip=None, **kwargs):
321    """
322    Used to recursively copy a directory from the salt master
323
324    CLI Example:
325
326    .. code-block:: bash
327
328        salt '*' cp.get_dir salt://path/to/dir/ /minion/dest
329
330    get_dir supports the same template and gzip arguments as get_file.
331    """
332    (path, dest) = _render_filenames(path, dest, saltenv, template, **kwargs)
333
334    return _client().get_dir(path, dest, saltenv, gzip)
335
336
337def get_url(path, dest="", saltenv="base", makedirs=False, source_hash=None):
338    """
339    .. versionchanged:: 2018.3.0
340        ``dest`` can now be a directory
341
342    Used to get a single file from a URL.
343
344    path
345        A URL to download a file from. Supported URL schemes are: ``salt://``,
346        ``http://``, ``https://``, ``ftp://``, ``s3://``, ``swift://`` and
347        ``file://`` (local filesystem). If no scheme was specified, this is
348        equivalent of using ``file://``.
349        If a ``file://`` URL is given, the function just returns absolute path
350        to that file on a local filesystem.
351        The function returns ``False`` if Salt was unable to fetch a file from
352        a ``salt://`` URL.
353
354    dest
355        The default behaviour is to write the fetched file to the given
356        destination path. If this parameter is omitted or set as empty string
357        (``''``), the function places the remote file on the local filesystem
358        inside the Minion cache directory and returns the path to that file.
359
360        .. note::
361
362            To simply return the file contents instead, set destination to
363            ``None``. This works with ``salt://``, ``http://``, ``https://``
364            and ``file://`` URLs. The files fetched by ``http://`` and
365            ``https://`` will not be cached.
366
367    saltenv : base
368        Salt fileserver environment from which to retrieve the file. Ignored if
369        ``path`` is not a ``salt://`` URL.
370
371    source_hash
372        If ``path`` is an http(s) or ftp URL and the file exists in the
373        minion's file cache, this option can be passed to keep the minion from
374        re-downloading the file if the cached copy matches the specified hash.
375
376        .. versionadded:: 2018.3.0
377
378    CLI Example:
379
380    .. code-block:: bash
381
382        salt '*' cp.get_url salt://my/file /tmp/this_file_is_mine
383        salt '*' cp.get_url http://www.slashdot.org /tmp/index.html
384    """
385    if isinstance(dest, str):
386        result = _client().get_url(
387            path, dest, makedirs, saltenv, source_hash=source_hash
388        )
389    else:
390        result = _client().get_url(
391            path, None, makedirs, saltenv, no_cache=True, source_hash=source_hash
392        )
393    if not result:
394        log.error(
395            "Unable to fetch file %s from saltenv %s.",
396            salt.utils.url.redact_http_basic_auth(path),
397            saltenv,
398        )
399    if result:
400        return salt.utils.stringutils.to_unicode(result)
401    return result
402
403
404def get_file_str(path, saltenv="base"):
405    """
406    Download a file from a URL to the Minion cache directory and return the
407    contents of that file
408
409    Returns ``False`` if Salt was unable to cache a file from a URL.
410
411    CLI Example:
412
413    .. code-block:: bash
414
415        salt '*' cp.get_file_str salt://my/file
416    """
417    fn_ = cache_file(path, saltenv)
418    if isinstance(fn_, str):
419        try:
420            with salt.utils.files.fopen(fn_, "r") as fp_:
421                return salt.utils.stringutils.to_unicode(fp_.read())
422        except OSError:
423            return False
424    return fn_
425
426
427def cache_file(path, saltenv="base", source_hash=None, verify_ssl=True):
428    """
429    Used to cache a single file on the Minion
430
431    Returns the location of the new cached file on the Minion
432
433    source_hash
434        If ``name`` is an http(s) or ftp URL and the file exists in the
435        minion's file cache, this option can be passed to keep the minion from
436        re-downloading the file if the cached copy matches the specified hash.
437
438        .. versionadded:: 2018.3.0
439
440    verify_ssl
441        If ``False``, remote https file sources (``https://``) and source_hash
442        will not attempt to validate the servers certificate. Default is True.
443
444        .. versionadded:: 3002
445
446    CLI Example:
447
448    .. code-block:: bash
449
450        salt '*' cp.cache_file salt://path/to/file
451
452    There are two ways of defining the fileserver environment (a.k.a.
453    ``saltenv``) from which to cache the file. One is to use the ``saltenv``
454    parameter, and the other is to use a querystring syntax in the ``salt://``
455    URL. The below two examples are equivalent:
456
457    .. code-block:: bash
458
459        salt '*' cp.cache_file salt://foo/bar.conf saltenv=config
460        salt '*' cp.cache_file salt://foo/bar.conf?saltenv=config
461
462    If the path being cached is a ``salt://`` URI, and the path does not exist,
463    then ``False`` will be returned.
464
465    .. note::
466        It may be necessary to quote the URL when using the querystring method,
467        depending on the shell being used to run the command.
468    """
469    path = salt.utils.data.decode(path)
470    saltenv = salt.utils.data.decode(saltenv)
471
472    contextkey = "{}_|-{}_|-{}".format("cp.cache_file", path, saltenv)
473
474    path_is_remote = (
475        urllib.parse.urlparse(path).scheme in salt.utils.files.REMOTE_PROTOS
476    )
477    try:
478        if path_is_remote and contextkey in __context__:
479            # Prevent multiple caches in the same salt run. Affects remote URLs
480            # since the master won't know their hash, so the fileclient
481            # wouldn't be able to prevent multiple caches if we try to cache
482            # the remote URL more than once.
483            if os.path.isfile(__context__[contextkey]):
484                return __context__[contextkey]
485            else:
486                # File is in __context__ but no longer exists in the minion
487                # cache, get rid of the context key and re-cache below.
488                # Accounts for corner case where file is removed from minion
489                # cache between cp.cache_file calls in the same salt-run.
490                __context__.pop(contextkey)
491    except AttributeError:
492        pass
493
494    path, senv = salt.utils.url.split_env(path)
495    if senv:
496        saltenv = senv
497
498    result = _client().cache_file(
499        path, saltenv, source_hash=source_hash, verify_ssl=verify_ssl
500    )
501    if not result:
502        log.error("Unable to cache file '%s' from saltenv '%s'.", path, saltenv)
503    if path_is_remote:
504        # Cache was successful, store the result in __context__ to prevent
505        # multiple caches (see above).
506        __context__[contextkey] = result
507    return result
508
509
510def cache_dest(url, saltenv="base"):
511    """
512    .. versionadded:: 3000
513
514    Returns the expected cache path for the file, if cached using
515    :py:func:`cp.cache_file <salt.modules.cp.cache_file>`.
516
517    .. note::
518        This only returns the _expected_ path, it does not tell you if the URL
519        is really cached. To check if the URL is cached, use
520        :py:func:`cp.is_cached <salt.modules.cp.is_cached>` instead.
521
522    CLI Examples:
523
524    .. code-block:: bash
525
526        salt '*' cp.cache_dest https://foo.com/bar.rpm
527        salt '*' cp.cache_dest salt://my/file
528        salt '*' cp.cache_dest salt://my/file saltenv=dev
529    """
530    return _client().cache_dest(url, saltenv)
531
532
533def cache_files(paths, saltenv="base"):
534    """
535    Used to gather many files from the Master, the gathered files will be
536    saved in the minion cachedir reflective to the paths retrieved from the
537    Master
538
539    CLI Example:
540
541    .. code-block:: bash
542
543        salt '*' cp.cache_files salt://pathto/file1,salt://pathto/file1
544
545    There are two ways of defining the fileserver environment (a.k.a.
546    ``saltenv``) from which to cache the files. One is to use the ``saltenv``
547    parameter, and the other is to use a querystring syntax in the ``salt://``
548    URL. The below two examples are equivalent:
549
550    .. code-block:: bash
551
552        salt '*' cp.cache_files salt://foo/bar.conf,salt://foo/baz.conf saltenv=config
553        salt '*' cp.cache_files salt://foo/bar.conf?saltenv=config,salt://foo/baz.conf?saltenv=config
554
555    The querystring method is less useful when all files are being cached from
556    the same environment, but is a good way of caching files from multiple
557    different environments in the same command. For example, the below command
558    will cache the first file from the ``config1`` environment, and the second
559    one from the ``config2`` environment.
560
561    .. code-block:: bash
562
563        salt '*' cp.cache_files salt://foo/bar.conf?saltenv=config1,salt://foo/bar.conf?saltenv=config2
564
565    .. note::
566        It may be necessary to quote the URL when using the querystring method,
567        depending on the shell being used to run the command.
568    """
569    return _client().cache_files(paths, saltenv)
570
571
572def cache_dir(
573    path, saltenv="base", include_empty=False, include_pat=None, exclude_pat=None
574):
575    """
576    Download and cache everything under a directory from the master
577
578
579    include_pat : None
580        Glob or regex to narrow down the files cached from the given path. If
581        matching with a regex, the regex must be prefixed with ``E@``,
582        otherwise the expression will be interpreted as a glob.
583
584        .. versionadded:: 2014.7.0
585
586    exclude_pat : None
587        Glob or regex to exclude certain files from being cached from the given
588        path. If matching with a regex, the regex must be prefixed with ``E@``,
589        otherwise the expression will be interpreted as a glob.
590
591        .. note::
592
593            If used with ``include_pat``, files matching this pattern will be
594            excluded from the subset of files defined by ``include_pat``.
595
596        .. versionadded:: 2014.7.0
597
598    CLI Examples:
599
600    .. code-block:: bash
601
602        salt '*' cp.cache_dir salt://path/to/dir
603        salt '*' cp.cache_dir salt://path/to/dir include_pat='E@*.py$'
604    """
605    return _client().cache_dir(path, saltenv, include_empty, include_pat, exclude_pat)
606
607
608def cache_master(saltenv="base"):
609    """
610    Retrieve all of the files on the master and cache them locally
611
612    CLI Example:
613
614    .. code-block:: bash
615
616        salt '*' cp.cache_master
617    """
618    return _client().cache_master(saltenv)
619
620
621def cache_local_file(path):
622    """
623    Cache a local file on the minion in the localfiles cache
624
625    CLI Example:
626
627    .. code-block:: bash
628
629        salt '*' cp.cache_local_file /etc/hosts
630    """
631    if not os.path.exists(path):
632        return ""
633
634    path_cached = is_cached(path)
635
636    # If the file has already been cached, return the path
637    if path_cached:
638        path_hash = hash_file(path)
639        path_cached_hash = hash_file(path_cached)
640
641        if path_hash["hsum"] == path_cached_hash["hsum"]:
642            return path_cached
643
644    # The file hasn't been cached or has changed; cache it
645    return _client().cache_local_file(path)
646
647
648def list_states(saltenv="base"):
649    """
650    List all of the available state modules in an environment
651
652    CLI Example:
653
654    .. code-block:: bash
655
656        salt '*' cp.list_states
657    """
658    return _client().list_states(saltenv)
659
660
661def list_master(saltenv="base", prefix=""):
662    """
663    List all of the files stored on the master
664
665    CLI Example:
666
667    .. code-block:: bash
668
669        salt '*' cp.list_master
670    """
671    return _client().file_list(saltenv, prefix)
672
673
674def list_master_dirs(saltenv="base", prefix=""):
675    """
676    List all of the directories stored on the master
677
678    CLI Example:
679
680    .. code-block:: bash
681
682        salt '*' cp.list_master_dirs
683    """
684    return _client().dir_list(saltenv, prefix)
685
686
687def list_master_symlinks(saltenv="base", prefix=""):
688    """
689    List all of the symlinks stored on the master
690
691    CLI Example:
692
693    .. code-block:: bash
694
695        salt '*' cp.list_master_symlinks
696    """
697    return _client().symlink_list(saltenv, prefix)
698
699
700def list_minion(saltenv="base"):
701    """
702    List all of the files cached on the minion
703
704    CLI Example:
705
706    .. code-block:: bash
707
708        salt '*' cp.list_minion
709    """
710    return _client().file_local_list(saltenv)
711
712
713def is_cached(path, saltenv="base"):
714    """
715    Returns the full path to a file if it is cached locally on the minion
716    otherwise returns a blank string
717
718    CLI Example:
719
720    .. code-block:: bash
721
722        salt '*' cp.is_cached salt://path/to/file
723    """
724    return _client().is_cached(path, saltenv)
725
726
727def hash_file(path, saltenv="base"):
728    """
729    Return the hash of a file, to get the hash of a file on the
730    salt master file server prepend the path with salt://<file on server>
731    otherwise, prepend the file with / for a local file.
732
733    CLI Example:
734
735    .. code-block:: bash
736
737        salt '*' cp.hash_file salt://path/to/file
738    """
739    path, senv = salt.utils.url.split_env(path)
740    if senv:
741        saltenv = senv
742
743    return _client().hash_file(path, saltenv)
744
745
746def stat_file(path, saltenv="base", octal=True):
747    """
748    Return the permissions of a file, to get the permissions of a file on the
749    salt master file server prepend the path with salt://<file on server>
750    otherwise, prepend the file with / for a local file.
751
752    CLI Example:
753
754    .. code-block:: bash
755
756        salt '*' cp.stat_file salt://path/to/file
757    """
758    path, senv = salt.utils.url.split_env(path)
759    if senv:
760        saltenv = senv
761
762    stat = _client().hash_and_stat_file(path, saltenv)[1]
763    if stat is None:
764        return stat
765    return salt.utils.files.st_mode_to_octal(stat[0]) if octal is True else stat[0]
766
767
768def push(path, keep_symlinks=False, upload_path=None, remove_source=False):
769    """
770    WARNING Files pushed to the master will have global read permissions..
771
772    Push a file from the minion up to the master, the file will be saved to
773    the salt master in the master's minion files cachedir
774    (defaults to ``/var/cache/salt/master/minions/minion-id/files``)
775
776    Since this feature allows a minion to push a file up to the master server
777    it is disabled by default for security purposes. To enable, set
778    ``file_recv`` to ``True`` in the master configuration file, and restart the
779    master.
780
781    keep_symlinks
782        Keep the path value without resolving its canonical form
783
784    upload_path
785        Provide a different path inside the master's minion files cachedir
786
787    remove_source
788        Remove the source file on the minion
789
790        .. versionadded:: 2016.3.0
791
792    CLI Example:
793
794    .. code-block:: bash
795
796        salt '*' cp.push /etc/fstab
797        salt '*' cp.push /etc/system-release keep_symlinks=True
798        salt '*' cp.push /etc/fstab upload_path='/new/path/fstab'
799        salt '*' cp.push /tmp/filename remove_source=True
800    """
801    log.debug("Trying to copy '%s' to master", path)
802    if "../" in path or not os.path.isabs(path):
803        log.debug("Path must be absolute, returning False")
804        return False
805    if not keep_symlinks:
806        path = os.path.realpath(path)
807    if not os.path.isfile(path):
808        log.debug("Path failed os.path.isfile check, returning False")
809        return False
810    auth = _auth()
811
812    if upload_path:
813        if "../" in upload_path:
814            log.debug("Path must be absolute, returning False")
815            log.debug("Bad path: %s", upload_path)
816            return False
817        load_path = upload_path.lstrip(os.sep)
818    else:
819        load_path = path.lstrip(os.sep)
820    # Normalize the path. This does not eliminate
821    # the possibility that relative entries will still be present
822    load_path_normal = os.path.normpath(load_path)
823
824    # If this is Windows and a drive letter is present, remove it
825    load_path_split_drive = os.path.splitdrive(load_path_normal)[1]
826
827    # Finally, split the remaining path into a list for delivery to the master
828    load_path_list = [_f for _f in load_path_split_drive.split(os.sep) if _f]
829
830    load = {
831        "cmd": "_file_recv",
832        "id": __opts__["id"],
833        "path": load_path_list,
834        "size": os.path.getsize(path),
835        "tok": auth.gen_token(b"salt"),
836    }
837
838    with salt.transport.client.ReqChannel.factory(__opts__) as channel:
839        with salt.utils.files.fopen(path, "rb") as fp_:
840            init_send = False
841            while True:
842                load["loc"] = fp_.tell()
843                load["data"] = fp_.read(__opts__["file_buffer_size"])
844                if not load["data"] and init_send:
845                    if remove_source:
846                        try:
847                            salt.utils.files.rm_rf(path)
848                            log.debug("Removing source file '%s'", path)
849                        except OSError:
850                            log.error("cp.push failed to remove file '%s'", path)
851                            return False
852                    return True
853                ret = channel.send(load)
854                if not ret:
855                    log.error(
856                        "cp.push Failed transfer failed. Ensure master has "
857                        "'file_recv' set to 'True' and that the file "
858                        "is not larger than the 'file_recv_size_max' "
859                        "setting on the master."
860                    )
861                    return ret
862                init_send = True
863
864
865def push_dir(path, glob=None, upload_path=None):
866    """
867    Push a directory from the minion up to the master, the files will be saved
868    to the salt master in the master's minion files cachedir (defaults to
869    ``/var/cache/salt/master/minions/minion-id/files``).  It also has a glob
870    for matching specific files using globbing.
871
872    .. versionadded:: 2014.7.0
873
874    Since this feature allows a minion to push files up to the master server it
875    is disabled by default for security purposes. To enable, set ``file_recv``
876    to ``True`` in the master configuration file, and restart the master.
877
878    upload_path
879        Provide a different path and directory name inside the master's minion
880        files cachedir
881
882    CLI Example:
883
884    .. code-block:: bash
885
886        salt '*' cp.push /usr/lib/mysql
887        salt '*' cp.push /usr/lib/mysql upload_path='/newmysql/path'
888        salt '*' cp.push_dir /etc/modprobe.d/ glob='*.conf'
889    """
890    if "../" in path or not os.path.isabs(path):
891        return False
892    tmpupload_path = upload_path
893    path = os.path.realpath(path)
894    if os.path.isfile(path):
895        return push(path, upload_path=upload_path)
896    else:
897        filelist = []
898        for root, _, files in salt.utils.path.os_walk(path):
899            filelist += [os.path.join(root, tmpfile) for tmpfile in files]
900        if glob is not None:
901            filelist = [
902                fi for fi in filelist if fnmatch.fnmatch(os.path.basename(fi), glob)
903            ]
904        if not filelist:
905            return False
906        for tmpfile in filelist:
907            if upload_path and tmpfile.startswith(path):
908                tmpupload_path = os.path.join(
909                    os.path.sep,
910                    upload_path.strip(os.path.sep),
911                    tmpfile.replace(path, "").strip(os.path.sep),
912                )
913            ret = push(tmpfile, upload_path=tmpupload_path)
914            if not ret:
915                return ret
916    return True
917