1#include "crypto.hh"
2#include "globals.hh"
3#include "store-api.hh"
4#include "util.hh"
5#include "nar-info-disk-cache.hh"
6#include "thread-pool.hh"
7#include "json.hh"
8#include "derivations.hh"
9
10#include <future>
11
12
13namespace nix {
14
15
16bool Store::isInStore(const Path & path) const
17{
18    return isInDir(path, storeDir);
19}
20
21
22bool Store::isStorePath(const Path & path) const
23{
24    return isInStore(path)
25        && path.size() >= storeDir.size() + 1 + storePathHashLen
26        && path.find('/', storeDir.size() + 1) == Path::npos;
27}
28
29
30void Store::assertStorePath(const Path & path) const
31{
32    if (!isStorePath(path))
33        throw Error(format("path ‘%1%’ is not in the Nix store") % path);
34}
35
36
37Path Store::toStorePath(const Path & path) const
38{
39    if (!isInStore(path))
40        throw Error(format("path ‘%1%’ is not in the Nix store") % path);
41    Path::size_type slash = path.find('/', storeDir.size() + 1);
42    if (slash == Path::npos)
43        return path;
44    else
45        return Path(path, 0, slash);
46}
47
48
49Path Store::followLinksToStore(const Path & _path) const
50{
51    Path path = absPath(_path);
52    while (!isInStore(path)) {
53        if (!isLink(path)) break;
54        string target = readLink(path);
55        path = absPath(target, dirOf(path));
56    }
57    if (!isInStore(path))
58        throw Error(format("path ‘%1%’ is not in the Nix store") % path);
59    return path;
60}
61
62
63Path Store::followLinksToStorePath(const Path & path) const
64{
65    return toStorePath(followLinksToStore(path));
66}
67
68
69string storePathToName(const Path & path)
70{
71    auto base = baseNameOf(path);
72    assert(base.size() == storePathHashLen || (base.size() > storePathHashLen && base[storePathHashLen] == '-'));
73    return base.size() == storePathHashLen ? "" : string(base, storePathHashLen + 1);
74}
75
76
77string storePathToHash(const Path & path)
78{
79    auto base = baseNameOf(path);
80    assert(base.size() >= storePathHashLen);
81    return string(base, 0, storePathHashLen);
82}
83
84
85void checkStoreName(const string & name)
86{
87    string validChars = "+-._?=";
88    /* Disallow names starting with a dot for possible security
89       reasons (e.g., "." and ".."). */
90    if (string(name, 0, 1) == ".")
91        throw Error(format("illegal name: ‘%1%’") % name);
92    for (auto & i : name)
93        if (!((i >= 'A' && i <= 'Z') ||
94              (i >= 'a' && i <= 'z') ||
95              (i >= '0' && i <= '9') ||
96              validChars.find(i) != string::npos))
97        {
98            throw Error(format("invalid character ‘%1%’ in name ‘%2%’")
99                % i % name);
100        }
101}
102
103
104/* Store paths have the following form:
105
106   <store>/<h>-<name>
107
108   where
109
110   <store> = the location of the Nix store, usually /nix/store
111
112   <name> = a human readable name for the path, typically obtained
113     from the name attribute of the derivation, or the name of the
114     source file from which the store path is created.  For derivation
115     outputs other than the default "out" output, the string "-<id>"
116     is suffixed to <name>.
117
118   <h> = base-32 representation of the first 160 bits of a SHA-256
119     hash of <s>; the hash part of the store name
120
121   <s> = the string "<type>:sha256:<h2>:<store>:<name>";
122     note that it includes the location of the store as well as the
123     name to make sure that changes to either of those are reflected
124     in the hash (e.g. you won't get /nix/store/<h>-name1 and
125     /nix/store/<h>-name2 with equal hash parts).
126
127   <type> = one of:
128     "text:<r1>:<r2>:...<rN>"
129       for plain text files written to the store using
130       addTextToStore(); <r1> ... <rN> are the references of the
131       path.
132     "source"
133       for paths copied to the store using addToStore() when recursive
134       = true and hashAlgo = "sha256"
135     "output:<id>"
136       for either the outputs created by derivations, OR paths copied
137       to the store using addToStore() with recursive != true or
138       hashAlgo != "sha256" (in that case "source" is used; it's
139       silly, but it's done that way for compatibility).  <id> is the
140       name of the output (usually, "out").
141
142   <h2> = base-16 representation of a SHA-256 hash of:
143     if <type> = "text:...":
144       the string written to the resulting store path
145     if <type> = "source":
146       the serialisation of the path from which this store path is
147       copied, as returned by hashPath()
148     if <type> = "output:<id>":
149       for non-fixed derivation outputs:
150         the derivation (see hashDerivationModulo() in
151         primops.cc)
152       for paths copied by addToStore() or produced by fixed-output
153       derivations:
154         the string "fixed:out:<rec><algo>:<hash>:", where
155           <rec> = "r:" for recursive (path) hashes, or "" for flat
156             (file) hashes
157           <algo> = "md5", "sha1" or "sha256"
158           <hash> = base-16 representation of the path or flat hash of
159             the contents of the path (or expected contents of the
160             path for fixed-output derivations)
161
162   It would have been nicer to handle fixed-output derivations under
163   "source", e.g. have something like "source:<rec><algo>", but we're
164   stuck with this for now...
165
166   The main reason for this way of computing names is to prevent name
167   collisions (for security).  For instance, it shouldn't be feasible
168   to come up with a derivation whose output path collides with the
169   path for a copied source.  The former would have a <s> starting with
170   "output:out:", while the latter would have a <s> starting with
171   "source:".
172*/
173
174
175Path Store::makeStorePath(const string & type,
176    const Hash & hash, const string & name) const
177{
178    /* e.g., "source:sha256:1abc...:/nix/store:foo.tar.gz" */
179    string s = type + ":sha256:" + printHash(hash) + ":"
180        + storeDir + ":" + name;
181
182    checkStoreName(name);
183
184    return storeDir + "/"
185        + printHash32(compressHash(hashString(htSHA256, s), 20))
186        + "-" + name;
187}
188
189
190Path Store::makeOutputPath(const string & id,
191    const Hash & hash, const string & name) const
192{
193    return makeStorePath("output:" + id, hash,
194        name + (id == "out" ? "" : "-" + id));
195}
196
197
198Path Store::makeFixedOutputPath(bool recursive,
199    const Hash & hash, const string & name) const
200{
201    return hash.type == htSHA256 && recursive
202        ? makeStorePath("source", hash, name)
203        : makeStorePath("output:out", hashString(htSHA256,
204                "fixed:out:" + (recursive ? (string) "r:" : "") +
205                printHashType(hash.type) + ":" + printHash(hash) + ":"),
206            name);
207}
208
209
210Path Store::makeTextPath(const string & name, const Hash & hash,
211    const PathSet & references) const
212{
213    assert(hash.type == htSHA256);
214    /* Stuff the references (if any) into the type.  This is a bit
215       hacky, but we can't put them in `s' since that would be
216       ambiguous. */
217    string type = "text";
218    for (auto & i : references) {
219        type += ":";
220        type += i;
221    }
222    return makeStorePath(type, hash, name);
223}
224
225
226std::pair<Path, Hash> Store::computeStorePathForPath(const Path & srcPath,
227    bool recursive, HashType hashAlgo, PathFilter & filter) const
228{
229    Hash h = recursive ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath);
230    string name = baseNameOf(srcPath);
231    Path dstPath = makeFixedOutputPath(recursive, h, name);
232    return std::pair<Path, Hash>(dstPath, h);
233}
234
235
236Path Store::computeStorePathForText(const string & name, const string & s,
237    const PathSet & references) const
238{
239    return makeTextPath(name, hashString(htSHA256, s), references);
240}
241
242
243Store::Store(const Params & params)
244    : Config(params)
245    , state({(size_t) pathInfoCacheSize})
246{
247}
248
249
250std::string Store::getUri()
251{
252    return "";
253}
254
255
256bool Store::isValidPath(const Path & storePath)
257{
258    auto hashPart = storePathToHash(storePath);
259
260    {
261        auto state_(state.lock());
262        auto res = state_->pathInfoCache.get(hashPart);
263        if (res) {
264            stats.narInfoReadAverted++;
265            return *res != 0;
266        }
267    }
268
269    if (diskCache) {
270        auto res = diskCache->lookupNarInfo(getUri(), hashPart);
271        if (res.first != NarInfoDiskCache::oUnknown) {
272            stats.narInfoReadAverted++;
273            auto state_(state.lock());
274            state_->pathInfoCache.upsert(hashPart,
275                res.first == NarInfoDiskCache::oInvalid ? 0 : res.second);
276            return res.first == NarInfoDiskCache::oValid;
277        }
278    }
279
280    bool valid = isValidPathUncached(storePath);
281
282    if (diskCache && !valid)
283        // FIXME: handle valid = true case.
284        diskCache->upsertNarInfo(getUri(), hashPart, 0);
285
286    return valid;
287}
288
289
290/* Default implementation for stores that only implement
291   queryPathInfoUncached(). */
292bool Store::isValidPathUncached(const Path & path)
293{
294    try {
295        queryPathInfo(path);
296        return true;
297    } catch (InvalidPath &) {
298        return false;
299    }
300}
301
302
303ref<const ValidPathInfo> Store::queryPathInfo(const Path & storePath)
304{
305    std::promise<ref<ValidPathInfo>> promise;
306
307    queryPathInfo(storePath,
308        [&](ref<ValidPathInfo> info) {
309            promise.set_value(info);
310        },
311        [&](std::exception_ptr exc) {
312            promise.set_exception(exc);
313        });
314
315    return promise.get_future().get();
316}
317
318
319void Store::queryPathInfo(const Path & storePath,
320    std::function<void(ref<ValidPathInfo>)> success,
321    std::function<void(std::exception_ptr exc)> failure)
322{
323    auto hashPart = storePathToHash(storePath);
324
325    try {
326
327        {
328            auto res = state.lock()->pathInfoCache.get(hashPart);
329            if (res) {
330                stats.narInfoReadAverted++;
331                if (!*res)
332                    throw InvalidPath(format("path ‘%s’ is not valid") % storePath);
333                return success(ref<ValidPathInfo>(*res));
334            }
335        }
336
337        if (diskCache) {
338            auto res = diskCache->lookupNarInfo(getUri(), hashPart);
339            if (res.first != NarInfoDiskCache::oUnknown) {
340                stats.narInfoReadAverted++;
341                {
342                    auto state_(state.lock());
343                    state_->pathInfoCache.upsert(hashPart,
344                        res.first == NarInfoDiskCache::oInvalid ? 0 : res.second);
345                    if (res.first == NarInfoDiskCache::oInvalid ||
346                        (res.second->path != storePath && storePathToName(storePath) != ""))
347                        throw InvalidPath(format("path ‘%s’ is not valid") % storePath);
348                }
349                return success(ref<ValidPathInfo>(res.second));
350            }
351        }
352
353    } catch (std::exception & e) {
354        return callFailure(failure);
355    }
356
357    queryPathInfoUncached(storePath,
358        [this, storePath, hashPart, success, failure](std::shared_ptr<ValidPathInfo> info) {
359
360            if (diskCache)
361                diskCache->upsertNarInfo(getUri(), hashPart, info);
362
363            {
364                auto state_(state.lock());
365                state_->pathInfoCache.upsert(hashPart, info);
366            }
367
368            if (!info
369                || (info->path != storePath && storePathToName(storePath) != ""))
370            {
371                stats.narInfoMissing++;
372                return failure(std::make_exception_ptr(InvalidPath(format("path ‘%s’ is not valid") % storePath)));
373            }
374
375            callSuccess(success, failure, ref<ValidPathInfo>(info));
376
377        }, failure);
378}
379
380
381PathSet Store::queryValidPaths(const PathSet & paths, bool maybeSubstitute)
382{
383    struct State
384    {
385        size_t left;
386        PathSet valid;
387        std::exception_ptr exc;
388    };
389
390    Sync<State> state_(State{paths.size(), PathSet()});
391
392    std::condition_variable wakeup;
393
394    for (auto & path : paths)
395        queryPathInfo(path,
396            [path, &state_, &wakeup](ref<ValidPathInfo> info) {
397                auto state(state_.lock());
398                state->valid.insert(path);
399                assert(state->left);
400                if (!--state->left)
401                    wakeup.notify_one();
402            },
403            [path, &state_, &wakeup](std::exception_ptr exc) {
404                auto state(state_.lock());
405                try {
406                    std::rethrow_exception(exc);
407                } catch (InvalidPath &) {
408                } catch (...) {
409                    state->exc = exc;
410                }
411                assert(state->left);
412                if (!--state->left)
413                    wakeup.notify_one();
414            });
415
416    while (true) {
417        auto state(state_.lock());
418        if (!state->left) {
419            if (state->exc) std::rethrow_exception(state->exc);
420            return state->valid;
421        }
422        state.wait(wakeup);
423    }
424}
425
426
427/* Return a string accepted by decodeValidPathInfo() that
428   registers the specified paths as valid.  Note: it's the
429   responsibility of the caller to provide a closure. */
430string Store::makeValidityRegistration(const PathSet & paths,
431    bool showDerivers, bool showHash)
432{
433    string s = "";
434
435    for (auto & i : paths) {
436        s += i + "\n";
437
438        auto info = queryPathInfo(i);
439
440        if (showHash) {
441            s += printHash(info->narHash) + "\n";
442            s += (format("%1%\n") % info->narSize).str();
443        }
444
445        Path deriver = showDerivers ? info->deriver : "";
446        s += deriver + "\n";
447
448        s += (format("%1%\n") % info->references.size()).str();
449
450        for (auto & j : info->references)
451            s += j + "\n";
452    }
453
454    return s;
455}
456
457
458void Store::pathInfoToJSON(JSONPlaceholder & jsonOut, const PathSet & storePaths,
459    bool includeImpureInfo, bool showClosureSize)
460{
461    auto jsonList = jsonOut.list();
462
463    for (auto storePath : storePaths) {
464        auto info = queryPathInfo(storePath);
465        storePath = info->path;
466
467        auto jsonPath = jsonList.object();
468        jsonPath
469            .attr("path", storePath)
470            .attr("narHash", info->narHash.to_string())
471            .attr("narSize", info->narSize);
472
473        {
474            auto jsonRefs = jsonPath.list("references");
475            for (auto & ref : info->references)
476                jsonRefs.elem(ref);
477        }
478
479        if (info->ca != "")
480            jsonPath.attr("ca", info->ca);
481
482        if (showClosureSize)
483            jsonPath.attr("closureSize", getClosureSize(storePath));
484
485        if (includeImpureInfo) {
486
487            if (info->deriver != "")
488                jsonPath.attr("deriver", info->deriver);
489
490            if (info->registrationTime)
491                jsonPath.attr("registrationTime", info->registrationTime);
492
493            if (info->ultimate)
494                jsonPath.attr("ultimate", info->ultimate);
495
496            if (!info->sigs.empty()) {
497                auto jsonSigs = jsonPath.list("signatures");
498                for (auto & sig : info->sigs)
499                    jsonSigs.elem(sig);
500            }
501
502        }
503    }
504}
505
506
507unsigned long long Store::getClosureSize(const Path & storePath)
508{
509    unsigned long long totalSize = 0;
510    PathSet closure;
511    computeFSClosure(storePath, closure, false, false);
512    for (auto & p : closure)
513        totalSize += queryPathInfo(p)->narSize;
514    return totalSize;
515}
516
517
518const Store::Stats & Store::getStats()
519{
520    {
521        auto state_(state.lock());
522        stats.pathInfoCacheSize = state_->pathInfoCache.size();
523    }
524    return stats;
525}
526
527
528void Store::buildPaths(const PathSet & paths, BuildMode buildMode)
529{
530    for (auto & path : paths)
531        if (isDerivation(path))
532            unsupported();
533
534    if (queryValidPaths(paths).size() != paths.size())
535        unsupported();
536}
537
538
539void copyStorePath(ref<Store> srcStore, ref<Store> dstStore,
540    const Path & storePath, bool repair, bool dontCheckSigs)
541{
542    auto info = srcStore->queryPathInfo(storePath);
543
544    StringSink sink;
545    srcStore->narFromPath({storePath}, sink);
546
547    if (!info->narHash && dontCheckSigs) {
548        auto info2 = make_ref<ValidPathInfo>(*info);
549        info2->narHash = hashString(htSHA256, *sink.s);
550        info = info2;
551    }
552
553    assert(info->narHash);
554
555    if (info->ultimate) {
556        auto info2 = make_ref<ValidPathInfo>(*info);
557        info2->ultimate = false;
558        info = info2;
559    }
560
561    assert(info->narHash);
562
563    dstStore->addToStore(*info, sink.s, repair, dontCheckSigs);
564}
565
566
567void copyClosure(ref<Store> srcStore, ref<Store> dstStore,
568    const PathSet & storePaths, bool repair, bool dontCheckSigs)
569{
570    PathSet closure;
571    for (auto & path : storePaths)
572        srcStore->computeFSClosure(path, closure);
573
574    // FIXME: use copyStorePaths()
575
576    PathSet valid = dstStore->queryValidPaths(closure);
577
578    if (valid.size() == closure.size()) return;
579
580    Paths sorted = srcStore->topoSortPaths(closure);
581
582    Paths missing;
583    for (auto i = sorted.rbegin(); i != sorted.rend(); ++i)
584        if (!valid.count(*i)) missing.push_back(*i);
585
586    printMsg(lvlDebug, format("copying %1% missing paths") % missing.size());
587
588    for (auto & i : missing)
589        copyStorePath(srcStore, dstStore, i, repair, dontCheckSigs);
590}
591
592
593ValidPathInfo decodeValidPathInfo(std::istream & str, bool hashGiven)
594{
595    ValidPathInfo info;
596    getline(str, info.path);
597    if (str.eof()) { info.path = ""; return info; }
598    if (hashGiven) {
599        string s;
600        getline(str, s);
601        info.narHash = parseHash(htSHA256, s);
602        getline(str, s);
603        if (!string2Int(s, info.narSize)) throw Error("number expected");
604    }
605    getline(str, info.deriver);
606    string s; int n;
607    getline(str, s);
608    if (!string2Int(s, n)) throw Error("number expected");
609    while (n--) {
610        getline(str, s);
611        info.references.insert(s);
612    }
613    if (!str || str.eof()) throw Error("missing input");
614    return info;
615}
616
617
618string showPaths(const PathSet & paths)
619{
620    string s;
621    for (auto & i : paths) {
622        if (s.size() != 0) s += ", ";
623        s += "‘" + i + "’";
624    }
625    return s;
626}
627
628
629std::string ValidPathInfo::fingerprint() const
630{
631    if (narSize == 0 || !narHash)
632        throw Error(format("cannot calculate fingerprint of path ‘%s’ because its size/hash is not known")
633            % path);
634    return
635        "1;" + path + ";"
636        + printHashType(narHash.type) + ":" + printHash32(narHash) + ";"
637        + std::to_string(narSize) + ";"
638        + concatStringsSep(",", references);
639}
640
641
642void ValidPathInfo::sign(const SecretKey & secretKey)
643{
644    sigs.insert(secretKey.signDetached(fingerprint()));
645}
646
647
648bool ValidPathInfo::isContentAddressed(const Store & store) const
649{
650    auto warn = [&]() {
651        printError(format("warning: path ‘%s’ claims to be content-addressed but isn't") % path);
652    };
653
654    if (hasPrefix(ca, "text:")) {
655        auto hash = parseHash(std::string(ca, 5));
656        if (store.makeTextPath(storePathToName(path), hash, references) == path)
657            return true;
658        else
659            warn();
660    }
661
662    else if (hasPrefix(ca, "fixed:")) {
663        bool recursive = ca.compare(6, 2, "r:") == 0;
664        auto hash = parseHash(std::string(ca, recursive ? 8 : 6));
665        if (store.makeFixedOutputPath(recursive, hash, storePathToName(path)) == path)
666            return true;
667        else
668            warn();
669    }
670
671    return false;
672}
673
674
675size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
676{
677    if (isContentAddressed(store)) return maxSigs;
678
679    size_t good = 0;
680    for (auto & sig : sigs)
681        if (checkSignature(publicKeys, sig))
682            good++;
683    return good;
684}
685
686
687bool ValidPathInfo::checkSignature(const PublicKeys & publicKeys, const std::string & sig) const
688{
689    return verifyDetached(fingerprint(), sig, publicKeys);
690}
691
692
693Strings ValidPathInfo::shortRefs() const
694{
695    Strings refs;
696    for (auto & r : references)
697        refs.push_back(baseNameOf(r));
698    return refs;
699}
700
701
702std::string makeFixedOutputCA(bool recursive, const Hash & hash)
703{
704    return "fixed:" + (recursive ? (std::string) "r:" : "") + hash.to_string();
705}
706
707
708}
709
710
711#include "local-store.hh"
712#include "remote-store.hh"
713
714
715namespace nix {
716
717
718RegisterStoreImplementation::Implementations * RegisterStoreImplementation::implementations = 0;
719
720
721ref<Store> openStore(const std::string & uri_,
722    const Store::Params & extraParams)
723{
724    auto uri(uri_);
725    Store::Params params(extraParams);
726    auto q = uri.find('?');
727    if (q != std::string::npos) {
728        for (auto s : tokenizeString<Strings>(uri.substr(q + 1), "&")) {
729            auto e = s.find('=');
730            if (e != std::string::npos)
731                params[s.substr(0, e)] = s.substr(e + 1);
732        }
733        uri = uri_.substr(0, q);
734    }
735
736    for (auto fun : *RegisterStoreImplementation::implementations) {
737        auto store = fun(uri, params);
738        if (store) {
739            store->warnUnknownSettings();
740            return ref<Store>(store);
741        }
742    }
743
744    throw Error("don't know how to open Nix store ‘%s’", uri);
745}
746
747
748StoreType getStoreType(const std::string & uri, const std::string & stateDir)
749{
750    if (uri == "daemon") {
751        return tDaemon;
752    } else if (uri == "local") {
753        return tLocal;
754    } else if (uri == "" || uri == "auto") {
755        if (access(stateDir.c_str(), R_OK | W_OK) == 0)
756            return tLocal;
757        else if (pathExists(settings.nixDaemonSocketFile))
758            return tDaemon;
759        else
760            return tLocal;
761    } else {
762        return tOther;
763    }
764}
765
766
767static RegisterStoreImplementation regStore([](
768    const std::string & uri, const Store::Params & params)
769    -> std::shared_ptr<Store>
770{
771    switch (getStoreType(uri, get(params, "state", settings.nixStateDir))) {
772        case tDaemon:
773            return std::shared_ptr<Store>(std::make_shared<UDSRemoteStore>(params));
774        case tLocal:
775            return std::shared_ptr<Store>(std::make_shared<LocalStore>(params));
776        default:
777            return nullptr;
778    }
779});
780
781
782std::list<ref<Store>> getDefaultSubstituters()
783{
784    struct State {
785        bool done = false;
786        std::list<ref<Store>> stores;
787    };
788    static Sync<State> state_;
789
790    auto state(state_.lock());
791
792    if (state->done) return state->stores;
793
794    StringSet done;
795
796    auto addStore = [&](const std::string & uri) {
797        if (done.count(uri)) return;
798        done.insert(uri);
799        state->stores.push_back(openStore(uri));
800    };
801
802    for (auto uri : settings.substituters.get())
803        addStore(uri);
804
805    for (auto uri : settings.extraSubstituters.get())
806        addStore(uri);
807
808    state->done = true;
809
810    return state->stores;
811}
812
813
814void copyPaths(ref<Store> from, ref<Store> to, const PathSet & storePaths,
815    bool substitute, bool dontCheckSigs)
816{
817    PathSet valid = to->queryValidPaths(storePaths, substitute);
818
819    PathSet missing;
820    for (auto & path : storePaths)
821        if (!valid.count(path)) missing.insert(path);
822
823    std::string copiedLabel = "copied";
824
825    //logger->setExpected(copiedLabel, missing.size());
826
827    ThreadPool pool;
828
829    processGraph<Path>(pool,
830        PathSet(missing.begin(), missing.end()),
831
832        [&](const Path & storePath) {
833            if (to->isValidPath(storePath)) return PathSet();
834            return from->queryPathInfo(storePath)->references;
835        },
836
837        [&](const Path & storePath) {
838            checkInterrupt();
839
840            if (!to->isValidPath(storePath)) {
841                //Activity act(*logger, lvlInfo, format("copying ‘%s’...") % storePath);
842
843                copyStorePath(from, to, storePath, false, dontCheckSigs);
844
845                //logger->incProgress(copiedLabel);
846            } else
847                ;
848                //logger->incExpected(copiedLabel, -1);
849        });
850
851    pool.process();
852}
853
854
855}
856