cmd.cpp revision c21bc9afe403f52c189d2d6b79dedaf9dce6217b
123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn/*
223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * Copyright (C) 2015 The Android Open Source Project
323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn *
423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * Licensed under the Apache License, Version 2.0 (the "License");
523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * you may not use this file except in compliance with the License.
623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * You may obtain a copy of the License at
723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn *
823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn *      http://www.apache.org/licenses/LICENSE-2.0
923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn *
1023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * Unless required by applicable law or agreed to in writing, software
1123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * distributed under the License is distributed on an "AS IS" BASIS,
1223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * See the License for the specific language governing permissions and
1423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn * limitations under the License.
1523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn */
1623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
1723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#define LOG_TAG "cmd"
1823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
1923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <utils/Log.h>
2023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <binder/Parcel.h>
2123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <binder/ProcessState.h>
2223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <binder/IResultReceiver.h>
2323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <binder/IServiceManager.h>
241941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include <binder/IShellCallback.h>
2523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <binder/TextOutput.h>
263d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#include <utils/Condition.h>
273d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#include <utils/Mutex.h>
2823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <utils/Vector.h>
2923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
3023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <getopt.h>
3123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <stdlib.h>
3223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <stdio.h>
3323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <string.h>
3423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <unistd.h>
351941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include <fcntl.h>
3623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn#include <sys/time.h>
371941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include <errno.h>
381941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn
391941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include "selinux/selinux.h"
401941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include "selinux/android.h"
411941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn
421941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn#include <UniquePtr.h>
4323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
443d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#define DEBUG 0
453d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn
4623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackbornusing namespace android;
4723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
4823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackbornstatic int sort_func(const String16* lhs, const String16* rhs)
4923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn{
5023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    return lhs->compare(*rhs);
5123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn}
5223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
531941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackbornstruct SecurityContext_Delete {
541941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn    void operator()(security_context_t p) const {
551941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        freecon(p);
561941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn    }
571941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn};
581941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborntypedef UniquePtr<char[], SecurityContext_Delete> Unique_SecurityContext;
591941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn
601941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackbornclass MyShellCallback : public BnShellCallback
611941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn{
621941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackbornpublic:
63e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn    bool mActive = true;
64e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn
651941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn    virtual int openOutputFile(const String16& path, const String16& seLinuxContext) {
661941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        String8 path8(path);
671941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        char cwd[256];
681941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        getcwd(cwd, 256);
691941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        String8 fullPath(cwd);
701941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        fullPath.appendPath(path8);
71e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn        if (!mActive) {
72e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn            aerr << "Open attempt after active for: " << fullPath << endl;
73e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn            return -EPERM;
74e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn        }
751941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        int fd = open(fullPath.string(), O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU|S_IRWXG);
761941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        if (fd < 0) {
771941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            return fd;
781941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        }
791941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        if (is_selinux_enabled() && seLinuxContext.size() > 0) {
801941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            String8 seLinuxContext8(seLinuxContext);
811941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            security_context_t tmp = NULL;
821941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            int ret = getfilecon(fullPath.string(), &tmp);
831941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            Unique_SecurityContext context(tmp);
841941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
851941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                    "file", "write", NULL);
861941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            if (accessGranted != 0) {
871941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                close(fd);
881941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                aerr << "System server has no access to file context " << context.get()
891941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                        << " (from path " << fullPath.string() << ", context "
901941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                        << seLinuxContext8.string() << ")" << endl;
911941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn                return -EPERM;
921941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn            }
931941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        }
941941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn        return fd;
951941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn    }
961941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn};
971941a404d642b3dfaac365ba494f1e9912876f00Dianne Hackborn
9823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackbornclass MyResultReceiver : public BnResultReceiver
9923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn{
10023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackbornpublic:
1013d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    Mutex mMutex;
1023d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    Condition mCondition;
1033d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    bool mHaveResult = false;
1043d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    int32_t mResult = 0;
1053d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn
1063d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    virtual void send(int32_t resultCode) {
1073d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        AutoMutex _l(mMutex);
1083d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        mResult = resultCode;
1093d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        mHaveResult = true;
1103d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        mCondition.signal();
1113d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    }
1123d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn
1133d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    int32_t waitForResult() {
1143d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        AutoMutex _l(mMutex);
1153d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        while (!mHaveResult) {
1163d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            mCondition.wait(mMutex);
1173d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        }
1183d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        return mResult;
11923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
12023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn};
12123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
12223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackbornint main(int argc, char* const argv[])
12323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn{
12423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    signal(SIGPIPE, SIG_IGN);
12523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    sp<ProcessState> proc = ProcessState::self();
126c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // setThreadPoolMaxThreadCount(0) actually tells the kernel it's
127c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // not allowed to spawn any additional threads, but we still spawn
128c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // a binder thread from userspace when we call startThreadPool().
129c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // This is safe because we only have 2 callbacks, neither of which
130c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // block.
131c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    // See b/36066697 for rationale
132c21bc9afe403f52c189d2d6b79dedaf9dce6217bMartijn Coenen    proc->setThreadPoolMaxThreadCount(0);
13323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    proc->startThreadPool();
13423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
13523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    sp<IServiceManager> sm = defaultServiceManager();
13623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    fflush(stdout);
13723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    if (sm == NULL) {
1383d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        ALOGW("Unable to get default service manager!");
13923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        aerr << "cmd: Unable to get default service manager!" << endl;
14023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        return 20;
14123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
14223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
14323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    if (argc == 1) {
1443d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        aerr << "cmd: No service specified; use -l to list all services" << endl;
14523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        return 20;
14623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
14723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
14823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    if ((argc == 2) && (strcmp(argv[1], "-l") == 0)) {
14923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        Vector<String16> services = sm->listServices();
15023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        services.sort(sort_func);
15123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        aout << "Currently running services:" << endl;
15223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
15323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        for (size_t i=0; i<services.size(); i++) {
15423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn            sp<IBinder> service = sm->checkService(services[i]);
15523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn            if (service != NULL) {
15623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn                aout << "  " << services[i] << endl;
15723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn            }
15823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        }
15923eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        return 0;
16023eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
16123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
16223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    Vector<String16> args;
16323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    for (int i=2; i<argc; i++) {
16423eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        args.add(String16(argv[i]));
16523eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
16623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    String16 cmd = String16(argv[1]);
16723eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    sp<IBinder> service = sm->checkService(cmd);
16823eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    if (service == NULL) {
1693d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        ALOGW("Can't find service %s", argv[1]);
1703d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        aerr << "cmd: Can't find service: " << argv[1] << endl;
17123eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn        return 20;
17223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    }
17323eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn
174e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn    sp<MyShellCallback> cb = new MyShellCallback();
1753d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    sp<MyResultReceiver> result = new MyResultReceiver();
1763d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn
1773d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#if DEBUG
1783d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    ALOGD("cmd: Invoking %s in=%d, out=%d, err=%d", argv[1], STDIN_FILENO, STDOUT_FILENO,
1793d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            STDERR_FILENO);
1803d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#endif
181e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn
18223eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn    // TODO: block until a result is returned to MyResultReceiver.
1833d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    status_t err = IBinder::shellCommand(service, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, args,
1843d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            cb, result);
1853d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    if (err < 0) {
1863d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        const char* errstr;
1873d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        switch (err) {
1883d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            case BAD_TYPE: errstr = "Bad type"; break;
1893d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            case FAILED_TRANSACTION: errstr = "Failed transaction"; break;
1903d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            case FDS_NOT_ALLOWED: errstr = "File descriptors not allowed"; break;
1913d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            case UNEXPECTED_NULL: errstr = "Unexpected null"; break;
1923d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn            default: errstr = strerror(-err); break;
1933d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        }
1943d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        ALOGW("Failure calling service %s: %s (%d)", argv[1], errstr, -err);
1953d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        aout << "cmd: Failure calling service " << argv[1] << ": " << errstr << " ("
1963d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn                << (-err) << ")" << endl;
1973d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn        return err;
1983d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    }
199e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn
200e5ed19999d724b6f5131803ff56b3536349b58deDianne Hackborn    cb->mActive = false;
2013d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    status_t res = result->waitForResult();
2023d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#if DEBUG
2033d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    ALOGD("result=%d", (int)res);
2043d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn#endif
2053d9eb950b14ee6527a5a85d669bd03ce2bdcff32Dianne Hackborn    return res;
20623eb1e20cf59e1238b65cc108f31dfbfd85b2337Dianne Hackborn}
207