[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## For users who want to report an issue\nPlease read [this wiki page]\n(https://github.com/dnschneid/crouton/wiki/Common-issues-and-reporting)\nfor instructions on reporting an issue.\n\n## For contributors\nPlease read [this\nsection](https://github.com/dnschneid/crouton#i-want-to-be-a-contributor)\nof the README and the following relevant sections first.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "```\nPlease paste the output of the following command here: sudo edit-chroot -all\n```\n\n#### Please describe your issue:\n\n\n#### If known, describe the steps to reproduce the issue:\n"
  },
  {
    "path": ".gitignore",
    "content": "*.tar.*\n.*.swp\n.*.tmp\nnacl_sdk/\n/crouton*\n/test/run\n/releases/\n"
  },
  {
    "path": "AUTHORS",
    "content": "Google, Inc\n"
  },
  {
    "path": "CONTRIBUTORS",
    "content": "Aaron Zauner\nAlex Bennée\nAlfred Suleymanov\nAlfriadox\nAlireza Ghasemi\nAndrew Kanner\nAron Griffis\nAurelien Lourot\nAvi Zajac\nBlaine Bublitz\nBraden Farmer\nbrannon\nbrmbrmcar\nBryce Thorup\nChris Galle\nChristopher Mårtensson\nChris Varga\nChuan Ji\nCorey Garst\nDaniel Haber\nDavid Reveman\nDavid Schneider\nDennis Lockhart\ndimonf\ndumpweed\nEugene Y. Q. Shen\nGeorge Shank\nIcecream95\nIgor Bukanov\nJackMacWindows\nJake Waksbaum\nJavi Merino\njessaustin\nJim Tittsler\nJohan Lorentzon\nJohn Tantalo\njoshua stein\nJustin Frankel\nJustin Guy\nKenny Strawn\nKerwin Hui\nLef Ioannidis\nMagnus Nyberg\nMasaki Muranaka\nMaurice van Kruchten\nMicah Lee\nMichael Mattioli\nMichael Moss\nMichael Orr\nMike Kasick\nMikito Takada\nMiles Whittaker\nNeal McBurnett\nNevada Romsdahl\nNicolas Boichat\nPete Baldridge\nPhillip Pearson\nRich Murphey\nRicky Brent\nRyan Fowler\nSamuel Dionne-Riel\nSimon Podhajsky\nStephen Barber\nSteve Desmond\nSteven Maude\nSteven Merrill\nTed Matsumura\nTobbe Lundberg\nTom Dunlap\nTony Xue\nWilliam Kong\nWilliam Ransohoff\nWilliam W. Wu\nYang Wang\nYu-Hsi Chiang\nYuri Pole\nYushin Washio\nzguithues\nZopolis4\n"
  },
  {
    "path": "LICENSE",
    "content": "// Copyright (c) 2016 The crouton Authors. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nTARGET = crouton\nEXTTARGET = crouton.zip\nLIBS = src/freon.c\nLIBSTARGETS = $(patsubst src/%.c, crouton%.so, $(LIBS))\nSRCTARGETS = $(patsubst src/%.c,crouton%,$(filter-out $(LIBS),$(wildcard src/*.c)))\nCONTRIBUTORS = CONTRIBUTORS\nWRAPPER = build/wrapper.sh\nSCRIPTS_NOSYM := \\\n\t$(wildcard host-bin/*) \\\n\t$(wildcard installer/*.sh) installer/functions \\\n\t$(wildcard installer/*/*) \\\n\t$(wildcard src/*) \\\n\t$(wildcard targets/*)\nSCRIPTS := \\\n\t$(wildcard chroot-bin/*) \\\n\t$(wildcard chroot-etc/*)\nEXTPEXE = host-ext/crouton/kiwi.pexe\nEXTPEXESOURCES = $(wildcard host-ext/nacl_src/*.h) \\\n\t\t\t\t $(wildcard host-ext/nacl_src/*.cc)\nEXTSOURCES = $(wildcard host-ext/crouton/*)\nBOOTSTRAPS := $(wildcard installer/*/bootstrap)\nBUILDDIR = crouton.build\nGENVERSION = build/genversion.sh\nCONTRIBUTORSSED = build/CONTRIBUTORS.sed\nRELEASE = build/release.sh\nVERSION = 1\nTARPARAMS ?= -j\n\nCFLAGS=-g -Wall -Werror -Wno-error=unused-function -Os\n\ncroutonfbserver_LIBS = -lX11 -lXdamage -lXext -lXfixes -lXtst\ncroutonxi2event_LIBS = -lX11 -lXi\ncroutonfreon.so_LIBS = -ldl -ldrm -I/usr/include/libdrm\n\ncroutonwebsocket_DEPS = src/websocket.h\ncroutonfbserver_DEPS = src/websocket.h\n\nifeq ($(wildcard .git/HEAD),)\n    GITHEAD :=\nelse\n    GITHEADFILE := .git/refs/heads/$(shell cut -d/ -f3 '.git/HEAD')\n    ifeq ($(wildcard $(GITHEADFILE)),)\n        GITHEAD := .git/HEAD\n    else\n        GITHEAD := .git/HEAD .git/refs/heads/$(shell cut -d/ -f3 '.git/HEAD')\n    endif\nendif\n\n$(TARGET): $(WRAPPER) $(BUILDDIR) $(GENVERSION) $(GITHEAD) Makefile\n\t{ \\\n\t\tsed -e \"s/\\$$TARPARAMS/$(TARPARAMS)/\" \\\n\t\t\t-e \"s/VERSION=.*/VERSION='$(shell $(GENVERSION) $(VERSION))'/\" \\\n\t\t\t$(WRAPPER) \\\n\t\t&& (cd $(BUILDDIR) && tar --owner=root --group=root -c $(TARPARAMS) *)\\\n\t\t&& chmod +x /dev/stdout \\\n\t;} > $(TARGET) || ! rm -f $(TARGET)\n\n$(BUILDDIR): $(SCRIPTS) $(SCRIPTS_NOSYM) Makefile\n\trm -rf $(BUILDDIR) && mkdir -p $(BUILDDIR) \\\n\t&& cp -at $(BUILDDIR) --parents $(SCRIPTS) \\\n\t&& cp -Lprt $(BUILDDIR) --parents $(SCRIPTS_NOSYM) \\\n\t&& for bootstrap in $(BOOTSTRAPS); do \\\n\t\ttmp=$(BUILDDIR); \\\n\t\t[ -h \"$$bootstrap\" ] && continue; \\\n\t\techo \"Preparing bootstrap dependencies for $$bootstrap\" >&2; \\\n\t\ttmp=$(BUILDDIR) sh -e \"$$bootstrap\" \\\n\t\t\t|| ! rm -rf $(BUILDDIR) || exit 1; \\\n\tdone\n\n$(EXTTARGET): $(EXTSOURCES) Makefile\n\trm -f $(EXTTARGET) && zip -q --junk-paths $(EXTTARGET) $(EXTSOURCES)\n\n$(EXTPEXE): $(EXTPEXESOURCES)\n\t$(MAKE) -C host-ext/nacl_src\n\n$(SRCTARGETS): $(patsubst crouton%,src/%.c,$@) $($@_DEPS) Makefile\n\tgcc $(CFLAGS) $(patsubst crouton%,src/%.c,$@) $($@_LIBS) -o $@\n\n$(LIBSTARGETS): $(patsubst crouton%.so,src/%.c,$@) $($@_DEPS) Makefile\n\tgcc $(CFLAGS) -shared -fPIC $(patsubst crouton%.so,src/%.c,$@) $($@_LIBS) -o $@\n\nextension: $(EXTTARGET)\n\n$(CONTRIBUTORS): $(GITHEAD) $(CONTRIBUTORSSED)\n\tgit shortlog -s | sed -f $(CONTRIBUTORSSED) | sort -uf > $(CONTRIBUTORS)\n\ncontributors: $(CONTRIBUTORS)\n\nrelease: $(CONTRIBUTORS) $(TARGET) $(RELEASE)\n\t[ ! -d .git ] || git status | grep -q 'working [a-z]* clean' || \\\n\t\t{ echo \"There are uncommitted changes. Aborting!\" 1>&2; exit 2; }\n\t$(RELEASE) $(TARGET)\n\nforce-release: $(CONTRIBUTORS) $(TARGET) $(RELEASE)\n\t$(RELEASE) -f $(TARGET)\n\nall: $(TARGET) $(SRCTARGETS) $(LIBSTARGETS) $(EXTTARGET)\n\nclean:\n\trm -f $(TARGET) $(EXTTARGET) $(SRCTARGETS) $(LIBSTARGETS)\n\trm -rf $(BUILDDIR)\n\n.PHONY: all clean contributors extension release force-release\n"
  },
  {
    "path": "README.md",
    "content": "# crouton: Chromium OS Universal Chroot Environment\n\ncrouton is a set of scripts that bundle up into an easy-to-use,\nChromium OS-centric chroot generator. Ubuntu, Debian, and Kali are supported\n(using debootstrap behind the scenes), but \"Chromium OS Debian and Ubuntu (plus\none distro) EOL'd Chroot Environment\" doesn't acronymize as well (crodupodece is\nadmittedly pretty fun to say, though).\n\n### 🪦 crouton is now end-of-life 🪦\n\nAll good things must come to an end, and considering\n * Chromium OS's introduction of increasingly strict shell safeguards,\n * the [change in cras's build tools](https://github.com/dnschneid/crouton/issues/4958),\n * the [removal of manifest v2 extensions](https://github.com/dnschneid/crouton/pull/5094),\n * the [removal of PNaCl, breaking xiwi](https://github.com/dnschneid/crouton/issues/5130),\n * oh, and Chromium OS being replaced by Android\n\nthere's really not much to gain from continued development. Put another way, the\nproverbial mixed salad is just about out of tasty crunchy bits, and the\nremaining morsels have gone a bit stale. And for some reason someone's about to\nswap all the lettuce out for onion rings? Point is, it's time to stop mixing\ndressings. Unless it's ranch, I guess, since that goes with just about anything.\nBut this is crouton, not some Ready-for-Android Native Chroot Host, alas.\n\nAnyway, this means that:\n * The repo is now locked, and no further changes will be considered.\n * Eventually someone will want the latest Ubuntu added to the release list. See\n   [this commit](https://github.com/dnschneid/crouton/commit/6d80f57b91c39d10b29fde861aac5a2b5b9b3910)\n   for an example of how to do it on your own copy.\n * Sometime around July 2025, the GitHub project will be archived, making the\n   issue tracker, discussions, and wiki read-only.\n * For the safety of users and stability of crouton's functionality for those on\n   EOL devices, offers to take over the dnschneid/crouton repo or extension will\n   be declined, and requests to change link destinations will be rejected.\n\nIf you have an EOL device, though, crouton is still a great match for you!\n * Chromium OS version 110 and earlier, everything should work! Breathe new life\n   into your old devices!!\n * Starting 111, crouton can't compile cras, so audio devices cannot be shared\n   with Chromium OS\n * Starting 117, sudo in crosh is disabled and you'll need to use VT-2\n * Starting 133 (139 for enterprise devices), manifest v2 is disabled and you\n   won't be able to run the extension (easy switching, clipboard sync, xiwi)\n * Also starting 133 (139 for enterprise devices), pnacl is disabled, so even if\n   you somehow got the extension working, xiwi won't function\n * Beyond that, it's anybody's guess as to what will break\n\n## Moving right along...\n\n:warning: **Steps to install crouton have changed!**  :warning:\n\nChromium OS has introduced several security features over the years that impede\nthe installation and usage of crouton. If your device is no longer receiving\nupdates, the steps below will likely work for you. However, if you are still\nhaving trouble, please try the [community-maintained instructions](https://github.com/dnschneid/crouton/wiki/Updated-Installation-Instructions-for-Crouton).\n\nIn addition, goo.gl is going away! That means the goo.gl/fd3zc you know and love\nhas been replaced with [git.io/JZEs0](https://git.io/JZEs0). That's a zero at\nthe end, if you were wondering. Both just point to\n[github](https://raw.githubusercontent.com/dnschneid/crouton/master/installer/crouton),\nso you can always just memorize the full link instead, which (fun fact) does not\ninclude any numbers at all!\n\n## \"crouton\"...an acronym?\n\nIt stands for _ChRomium Os Universal chrooT envirONment_\n...or something like that. Do capitals really matter if caps-lock has been\n(mostly) banished, and the keycaps are all lower-case?\n\nMoving on...\n\n\n## Who's this for?\n\nAnyone who wants to run straight Linux on their Chromium OS device, and doesn't\ncare about physical security. You're also better off having some knowledge of\nLinux tools and the command line in case things go funny, but it's not strictly\nnecessary.\n\n\n## What's a chroot?\n\nLike virtualization, chroots provide the guest OS with their own, segregated\nfile system to run in, allowing applications to run in a different binary\nenvironment from the host OS. Unlike virtualization, you are *not* booting a\nsecond OS; instead, the guest OS is running using the Chromium OS system. The\nbenefit to this is that there is zero speed penalty since everything is run\nnatively, and you aren't wasting RAM to boot two OSes at the same time. The\ndownside is that you must be running the correct chroot for your hardware, the\nsoftware must be compatible with Chromium OS's kernel, and machine resources are\ninextricably tied between the host Chromium OS and the guest OS. What this means\nis that while the chroot cannot directly access files outside of its view, it\n*can* access all of your hardware devices, including the entire contents of\nmemory. A root exploit in your guest OS will essentially have unfettered access\nto the rest of Chromium OS.\n\n...but hey, you can run [TuxRacer](https://en.wikipedia.org/wiki/Tux_Racer)!\n\n\n### What about dem crostinis though?\n\n[Crostini](https://chromium.googlesource.com/chromiumos/docs/+/HEAD/containers_and_vms.md)\nis an official project within Chromium OS to bring the Linux shell and apps to\nthe platform *in verified mode* with clean integration, multi-layered security,\nand all the polish you expect from Chromium OS proper.\n\nThat means compared to crouton, Crostini has official support, competent\nengineers, and code that looks a little less like ramen.  crouton, in its\ndefense, has wider device compatibility, enables direct hardware access, and is\nnamed after an objectively tastier bread-based food item.\n\nThere's a solid community on [Reddit](https://www.reddit.com/r/Crostini/) if\nyou'd like to try Crostini out.  If it works for you -- great!  No hard\nfeelings.  If in the end you decide that crouton suits you better, read on!\n\nNote: you can't get the best of both worlds by installing crouton inside of\nCrostini.  The technology (and life itself) just doesn't work that way.  Not to\nmention a crouton Crostini would look ridiculous and be impossible to eat\nwithout getting bits everywhere.\n\n\n## Prerequisites\n\nYou need a device running Chromium OS that has been switched to developer mode.\n\nFor instructions on how to do that, go to [this Chromium OS wiki page](https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices),\nclick on your device model and follow the steps in the *Entering Developer Mode*\nsection.\n\nNote that developer mode, in its default configuration, is *completely\ninsecure*, so don't expect a password in your chroot to keep anyone from your\ndata. crouton does support encrypting chroots, but the encryption is only as\nstrong as the quality of your passphrase. Consider this your warning.\n\nIt's also highly recommended that you install the [crouton extension](https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom),\nwhich, when combined with the `extension` or `xiwi` targets, provides much \nimproved integration with Chromium OS.\n\nThat's it! Surprised?\n\n\n## Usage\n\ncrouton is a powerful tool, and there are a *lot* of features, but basic usage\nis as simple as possible by design.\n\nIf you're just here to use crouton, you can grab the latest release from\n[https://git.io/JZEs0](https://git.io/JZEs0). Download it, pop open a\nshell (Ctrl+Alt+T, type `shell` and hit enter), make the installer executable\nwith `sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`, then launch\nit with `sudo crouton` to see the help text. See the \"examples\" section for some\nusage examples.\n\nIf you're modifying crouton, you'll probably want to clone or download the repo\ninto a subdirectory of `/usr/local` and then either run `installer/main.sh`\ndirectly, or use `make` to build your very own `crouton`. You can also download\nthe latest release, install it as above and run `crouton -x` to extract out the\njuicy scripts contained within, but you'll be missing build-time stuff like the\nMakefile. You also need to remember to place the unbundled scripts somewhere in\n`/usr/local` in order to be able to execute them.\n\ncrouton uses the concept of \"targets\" to decide what to install. While you will\nhave apt-get in your chroot, some targets may need minor hacks to avoid issues\nwhen running in the chrooted environment. As such, if you expect to want\nsomething that is fulfilled by a target, install that target when you make the\nchroot and you'll have an easier time.  Don't worry if you forget to include a\ntarget; you can always update the chroot later and add it. You can see the list\nof available targets by running `crouton -t help`.\n\nOnce you've set up your chroot, you can easily enter it using the\nnewly-installed `enter-chroot` command, or one of the target-specific\nstart\\* commands. Ta-da! That was easy.\n\n\n## Examples\n\n### The easy way (assuming you want an Ubuntu LTS with Xfce)\n\n  1. Download `crouton`\n  2. Open a shell (Ctrl+Alt+T, type `shell` and hit enter)\n  3. Copy the installer to an executable location by running\n     `sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`\n  4. Now that it's executable, run the installer itself: `sudo crouton -t xfce`\n  5. Wait patiently and answer the prompts like a good person.\n  6. Done! You can jump straight to your Xfce session by running\n     `sudo enter-chroot startxfce4` or, as a special shortcut, `sudo startxfce4`\n  7. Cycle through Chromium OS and your running graphical chroots using\n     Ctrl+Alt+Shift+Back and Ctrl+Alt+Shift+Forward.\n  8. Exit the chroot by logging out of Xfce.\n\n### With encryption!\n\n  1. Add the `-e` parameter when you run crouton to create an encrypted chroot\n     or encrypt a non-encrypted chroot.\n  2. You can get some extra protection on your chroot by storing the decryption\n     key separately from the place the chroot is stored. Use the `-k` parameter\n     to specify a file or directory to store the keys in (such as a USB drive or\n     SD card) when you create the chroot. Beware that if you lose this file,\n     your chroot will not be decryptable. That's kind of the point, of course.\n\n### Hey now, Ubuntu 16.04 is pretty old; I'm young and hip\n\n  1. The `-r` parameter specifies which distro release you want to use.\n  2. Run `crouton -r list` to list the recognized releases and which distros\n     they belong to.\n\n### Wasteful redundancies are wasteful: one clipboard, one browser, one window\n\n  1. Install the [crouton extension](https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom)\n     into Chromium OS.\n  2. Add the `extension` or `xiwi` version to your chroot.\n  3. Try some copy-pasta, or uninstall all your web browsers from the chroot.\n\n*Installing the extension and its target gives you synchronized clipboards, the\noption of using Chromium OS to handle URLs, and allows chroots to create\ngraphical sessions as Chromium OS windows.*\n\n### I don't always use Linux, but when I do, I use CLI\n\n  1. You can save a chunk of space by ditching X and just installing\n     command-line tools using `-t core` or `-t cli-extra`\n  2. Enter the chroot in as many crosh shells as you want simultaneously using\n     `sudo enter-chroot`\n  3. Use the [Crosh Window](https://chromewebstore.google.com/detail/crosh-window/nhbmpbdladcchdhkemlojfjdknjadhmh)\n     extension to keep Chromium OS from eating standard keyboard shortcuts.\n  4. If you installed cli-extra, `startcli` will launch a new VT right into the\n     chroot.\n\n### A new version of crouton came out; my chroot is therefore obsolete and sad\n\n  1. Exit the chroot if you have it open.\n  2. If you haven't already, download `crouton`, and copy it so it works:\n     `sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`\n  3. Update your chroot with `sudo crouton -u -n chrootname`. It will update\n     all installed targets.\n\n### I want to open my desktop in a window or a tab but I don't have the 'xiwi' target/xmethod.\n\n  1. Add 'xiwi' or any other target to an existing chroot with the `-u` option:\n     `sudo crouton -t xiwi -u -n chrootname`\n\n  This will also make 'xiwi' the default xmethod.\n\n  2. If you want to keep the 'xorg' xmethod as the default then pick it first:\n     `sudo sh crouton -t xorg,xiwi -u -n chrootname`\n\n### A backup a day keeps the price-gouging data restoration services away\n\n  1. `sudo edit-chroot -b chrootname` backs up your chroot to a timestamped\n     tarball in the current directory. Chroots are named either via the `-n`\n     parameter when created or by the release name if -n was not specified.\n  2. `sudo edit-chroot -r chrootname` restores the chroot from the most recent\n     timestamped tarball. You can explicitly specify the tarball with `-f`\n  3. If your machine is new, powerwashed, or held upside-down and shaken, you\n     can use the crouton installer to restore a chroot and relevant scripts:\n     `sudo crouton -f mybackup.tar.gz`\n\n*Unlike with Chromium OS, the data in your chroot isn't synced to the cloud.*\n\n### This chroot's name/location/password/existence sucks. How to fix?\n\n  1. Check out the `edit-chroot` command; it likely does what you need it to do.\n  2. If you set a Chromium OS root password, you can change it with\n     `sudo chromeos-setdevpasswd`\n  3. You can change the password inside your chroot with `passwd`\n\n### I want to install the chroot to another location\n\n  1. Use `-p` to specify the directory in which to install the chroot and\n     scripts. Be sure to quote or escape spaces.\n  2. When entering the chroot for the first time each boot, you will first need\n     to ensure the place you've installed the scripts is in a place that allows\n     executables to run. Determine the mountpoint by running\n     `df --output=target /path/to/enter-chroot`, then mark the mount exec with\n     `sudo mount -o remount,exec /path/to/mountpoint`.\n  3. You can then launch the chroot by specifying the full path of any of the\n     enter-chroot or start* scripts (i.e. `sudo /path/to/enter-chroot`), or use\n     the `-c` parameter to explicitly specify the chroots directory.\n\n*If for some reason you have to run the installer without touching the local\ndisk, you can (for the time being) run\n`curl -fL https://git.io/JZEs0 | sudo sh -s -- options_for_crouton_installer`.\nNote that this will definitely break in the near future, so don't depend on it.*\n\n### Downloading bootstrap files over and over again is a waste of time\n\n  1. Download `crouton`\n  2. Open a shell (Ctrl+Alt+T, type `shell` and hit enter)\n  3. Copy the installer to an executable location by running\n     `sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`\n  4. Now that it's executable, use the installer to build a bootstrap tarball:\n     `sudo crouton -d -f ~/Downloads/mybootstrap.tar.bz2`\n  5. Include the `-r` parameter if you want to specify for which release to\n     prepare a bootstrap.\n  6. You can then create chroots using the tarball by running\n     `sudo crouton -f ~/Downloads/mybootstrap.tar.bz2`. Make sure you also\n     specify the target environment with `-t`.\n\n*This is the quickest way to create multiple chroots at once, since you won't\nhave to determine and download the bootstrap files every time.*\n\n### Targets are cool. Abusing them for fun and profit is even cooler\n\n  1. You can make your own target files (start by copying one of the existing\n     ones) and then use them with any version of crouton via the `-T` parameter.\n\n*This is great for automating common tasks when creating chroots.*\n\n### Help! I've created a monster that must be slain!\n\n  1. The delete-chroot command is your sword, shield, and only true friend.\n     `sudo delete-chroot evilchroot`\n  2. It's actually just a shortcut to `sudo edit-chroot -d evilchroot`, which I\n     suppose makes it a bit of a deceptive Swiss Army knife friend...still good?\n\n\n## Tips\n\n  * Chroots are cheap! Create multiple ones using `-n`, break them, then make\n    new, better ones!\n  * You can change the distro mirror from the default by using `-m`\n  * Want to use a proxy? `-P` lets you specify one (or disable it).\n  * A script is installed in your chroot called `brightness`. You can assign\n    this to keyboard shortcuts to adjust the brightness of the screen (e.g.\n    `brightness up`) or keyboard (e.g. `brightness k down`).\n  * Multiple monitors will work fine in the chroot, but you may have to switch\n    to Chromium OS and back to enable them.\n  * You can make commands run in the background so that you can close the\n    terminal. This is particularly useful for desktop environments: try running\n    `sudo startxfce4 -b`\n  * Want to disable Chromium OS's power management? Run `croutonpowerd -i`\n  * Only want power management disabled for the duration of a command?\n    `croutonpowerd -i command and arguments` will automatically stop inhibiting\n    power management when the command exits.\n  * Have a Pixel or two or 4.352 million? `-t touch` improves touch support.\n  * Want to share some files and/or folders between Chromium OS and your chroot?\n    Check out the `/etc/crouton/shares` file, or read all about it in the wiki.\n  * Want more tips? Check the [wiki](https://github.com/dnschneid/crouton/wiki).\n\n\n## Issues?\n\nRunning another OS in a chroot is a pretty messy technique (although it's hidden\nbehind very pretty scripts), and while these scripts are relatively mature,\nChromium OS is changing all the time so problems are not surprising. Check the\nissue tracker and file a bug if your issue isn't there. When filing a new bug,\ninclude the output of `croutonversion` run from inside the chroot or, if you\ncannot mount your chroot, include the output of `cat /etc/lsb-release` from Crosh.\n\n\n## I want to be a Contributor!\n\nThat's great!  But before your code can be merged, you'll need to have signed\nthe [Individual Contributor License Agreement](https://cla.developers.google.com/clas/new?kind=KIND_INDIVIDUAL&domain=DOMAIN_GOOGLE).\nDon't worry, it only takes a minute and you'll definitely get to keep your\nfirstborn, probably.  If you've already signed it for contributing to Chromium\nor Chromium OS, you're already done.\n\nIf you don't know what to do with your time as an official Contributor, keep in\nmind that crouton is maintenance-only and will only be accepting a limited amount\nof changes.  That having been said, here's some suggestions:\n\n  * Really like a certain desktop environment? Fork crouton, add the target, and\n    let people know in the discussions area.\n  * Is your distro underrepresented? Want to contribute to the elusive and\n    mythical beast known as \"croagh\"? Fork crouton, add the distro, and people\n    will come.\n  * Discovered a bug lurking within the scripts, or a papercut that bothers you\n    just enough to make you want to actually do something about it? You guessed\n    it: fork crouton, fix everything, and create a pull request.\n  * Are most bugs too high-level for you to defeat? Grind up some\n    [EXP](https://en.wikipedia.org/wiki/Experience_point) by using\n    your fork to eat [pie](https://github.com/dnschneid/crouton/labels/pie).\n\n\n## Are there other, non-Contributory ways I can help?\n\nYes!\n\n\n## But how?\n\nThere's a way For Everyone to help!\n\n  * Something broken? File a bug! Bonus points if you try to fix it. It helps if\n    you provide the output of `croutonversion` (or the output of\n    `cat /etc/lsb-release` from Crosh) when you submit the bug.\n  * Look through [open issues](https://github.com/dnschneid/crouton/issues?state=open)\n    and see if there's a topic or application you happen to have experience\n    with. And then, preferably, share that experience with others.\n  * Find issues that need [wiki entries](https://github.com/dnschneid/crouton/issues?labels=needswiki&state=open,closed)\n    and add the relevant info to the [wiki](https://github.com/dnschneid/crouton/wiki).\n    Or just add things to/improve things in the wiki in general, but do try to\n    keep it relevant and organized.\n  * Really like a certain desktop environment, but not up for coding? Open or\n    comment on a bug with steps to get things working well.\n\n\n## License\n\ncrouton (including this eloquently-written README) is copyright &copy; 2016 The\ncrouton Authors. All rights reserved. Use of the source code included here is\ngoverned by a BSD-style license that can be found in the LICENSE file in the\nsource tree.\n"
  },
  {
    "path": "README.zh.md",
    "content": "# Crouton中文版教程\r\n\r\n> 基于crouton项目README的英文版进行汉化，部分内容没有进行汉化，建议有能力者优先阅读英文版本。\r\n\r\n## 简介\r\n\r\n​\tChroot是Chromium OS Universal Chroot Environment 的简写，是一系列脚本的合集，利用Linux的Chroot，在Chromebook上同时运行Chrome OS和某个Linux发行版。\r\n\r\n## Chroot介绍\r\n\r\n​\tChroot命令用来在指定的根目录下运行指令。Chroot的这种功能可以为第二系统提供一个隔离的文件系统，就像虚拟化一样，但是第二系统实际上仍然在主系统的文件系统下面工作，在进程和网络层面，chroot并没有进行隔离。\r\n\r\n​\t至于详细的内容，为什么不去问问[百度搜索](https://www.baidu.com/s?wd=chroot)？\r\n\r\n## 进入正题\r\n\r\n### 环境\r\n\r\n- **良好的**网络环境\r\n- 进入**开发者模式**的Chromebook，相关操作请进入[这个页面](https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices)（英文页面），点击对应的设备型号，按照*Entering Developer Mode*章节的步骤进行\r\n- 强烈建议安装[Crouton插件](https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom)，配合`extension`或者`xiwi`目标，可以提高第二系统与Chrome OS之间的交互体验\r\n\r\n### 用法\r\n\r\n1. 你需要从[这里](https://git.io/JZEs0)下载Crouton脚本。~~什么？下不下来？关我什么事~~\r\n\r\n2. 然后打开shell（`ctrl+alt+T`,在打开的窗口中输入`shell`，然后回车）。\r\n\r\n3. 输入`sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`，这一步是将下载下来的脚本安装到`/usr/local/bin`这个可执行目录里面。\r\n\r\n4. `sudo crouton`可以查看帮助，本教程**示例**部分会有一些命令使用举例。\r\n\r\n   如果你想对Crouton稍作修改，可以将本项目下载到`/usr/local`，直接运行`installer/main.sh`或者使用`make`进行编译。你也可以按上述四步安装crouton后，使用`crouton -x`将包含的脚本解压，不过那样的话你需要自己编写编译所需的文件，以及记住脚本所在的位置。\r\n\r\n   Crouton使用“目标”('targets')来决定安装什么。可用的目标可以运行`crouton -t help`来查看。\r\n\r\n   安装之后，可以输入`enter-chroot`，或者由你选择的安装目标所决定的 start* 命令。具体的命令，安装完成后终端会有介绍（英文）。  \r\n\r\n## 示例\r\n\r\n**简单示例（安装Ubuntu LTS，使用Xfce桌面环境）**\r\n\r\n1. 下载Crouton\r\n2. 打开shell（`ctrl+alt+T`,在打开的窗口中输入`shell`，然后回车）。\r\n3. 输入`sudo install -Dt /usr/local/bin -m 755 ~/Downloads/crouton`\r\n4. `sudo crouton -t xfce`\r\n5. 等吧，可以喝杯星巴克\r\n6. 安装完成后，使用`sudo enter-chroot startxfce4`，或者`sudo startxfce4`运行chroot，会自动跳至Xfce\r\n7. 登出/注销(logout)Xfce来退出chroot，**在Xfce里点击关机是没有用的**。\r\n\r\n**加密**\r\n\r\n1. 运行crouton是可以添加`-e`参数来创建一个加密的chroot环境，或者加密一个未加密的chroot环境\r\n2. 使用`-k`参数来指定储存密钥的路径\r\n\r\n**想用别的系统？**\r\n\r\n1. `-r`参数可以指定你想要使用的发行版和版本代号\r\n2. `crouton -r list`可以查看支持的发行版和版本代号（英文）\r\n\r\n**说好的“更好的交互体验”？**\r\n\r\n1. 在Chrome OS安装[Crouton插件](https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom)\r\n\r\n2. 在chroot环境中添加`extension`或者`xiwi`目标\r\n\r\n   这样可以同步chroot环境和主系统的剪贴板，允许chroot环境的程序在Chrome OS界面中窗口化运行。\r\n\r\n**只使用命令行**\r\n\r\n1. 指定安装目标时可以只使用`-t core`或者`-t cli-extra`\r\n2. 使用`sudo enter-chroot`进入chroot环境\r\n3. 使用[Crosh Window插件](https://chromewebstore.google.com/detail/crosh-window/nhbmpbdladcchdhkemlojfjdknjadhmh)，防止chroot命令行环境导致的快捷键失效\r\n\r\n**升级chroot环境**\r\n\r\n​\t使用`sudo crouton -u -n chrootname`来升级chroot环境中的系统。\r\n\r\n**安装后想添加一些安装目标？**\r\n\r\n​\t使用`-u`参数来添加安装目标。\r\n\r\n​\t比如，添加`xiwi`目标：`sudo crouton -t xiwi -u -n chrootname`\r\n\r\n​\t上述命令会让xiwi成为默认的[X窗口方法](https://baike.baidu.com/item/X%E7%AA%97%E5%8F%A3/1471357?fr=aladdin)（原默认方法为xorg），如果想让X窗口方法继续保持默认：\r\n\r\n​\t`sudo crouton -t xorg,xiwi -u -n chrootname`\r\n\r\n**备份**\r\n\r\n​\t`sudo edit-chroot -b chrootname`会在命令运行目录下生成chroot环境的tar格式的备份文件（带备份时间戳），Chroot环境的名字可以在安装时由`-n`参数指定，未指定时默认为所安装的Linux发行版版本代号（例如，默认的Ubuntu 16.04LTS版本代号为xenial）\r\n\r\n​\t`sudo edit-chroot -r chrootname`默认恢复最近一次的备份文件。可以用`-r`参数指定恢复文件\r\n\r\n​\t对全新或者重置过的电脑，可以使用Crouton的恢复命令：`sudo crouton -f mybackup.tar.gz`\r\n\r\n**更改安装位置**\r\n\r\n​\t`-p`参数可指定chroot的安装位置。\r\n\r\n​\t每次启动电脑后第一次启动chroot，请确定chroot的安装位置是可执行（executable）的：\r\n\r\n\t1. 确定挂载点：`df --output=target /path/to/enterchroot`\r\n \t2. 使挂载点可读写：`sudo mount -o remount,exec /path/to/mountpoint`\r\n\r\n**删除Chroot环境**\r\n\r\n​\t`sudo delete-chroot chrootname`\r\n\r\n## 使用提醒\r\n\r\n- 使用`-n`来指定Chroot环境的名字，可以创建多个chroot环境\r\n- 使用`-m`参数更改镜像源\r\n- `-P`参数开启/关闭Chroot环境的代理，仅支持http/https\r\n- chroot内置`brightness`脚本，可以：\r\n  - 调节屏幕亮度（比如，在chroot内运行`brightness up`）\r\n  - 调节背光键盘亮度（比如，在chroot内运行：`brightness k down`）\r\n- 使用多屏可能需要先切换到Chrome OS界面，然后再切换回来\r\n- 运行命令添加`-b`参数可以让chroot在后台运行，比如：`sudo startxfce4 -b`\r\n- `croutonpowerd -i`可以关闭Chrome OS的电源管理\r\n- `croutonpowerd -i command and arguments`可以指定在chroot执行某些命令时关闭Chrome OS的电源管理\r\n- `touch`安装目标可以改善触摸屏设备的使用体验\r\n- 本项目Wiki中有关于文件共享的介绍\r\n- [Wiki](https://github.com/dnschneid/crouton/wiki)中也有更多其他提示（英文）\r\n"
  },
  {
    "path": "build/CONTRIBUTORS.sed",
    "content": "s/^[ \\t0-9]*//\ns/^DennisL.*$/Dennis Lockhart/\ns/^drinkcat$/Nicolas Boichat/\ns/^divx118$/Maurice van Kruchten/\ns/^haberda$/Daniel Haber/\ns/^tedm$/Ted Matsumura/\ns/^magnus$/Magnus Nyberg/\ns/^eyqs$/Eugene Y. Q. Shen/\n/^nromsdahl$/d\n/^root$/d\n/^ttk153$/d\n/^lnxsrt$/d\n/\\?/d\n"
  },
  {
    "path": "build/genversion.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Outputs a version string with the specified prefix.\n\nif [ \"$#\" != 1 ]; then\n    echo \"Usage: ${0##*/} VERSION\" 1>&2\n    exit 1\nfi\n\nsource=''\n\n# Get the branch from git\ngit=\"`dirname \"$0\"`/../.git\"\nif [ -f \"$git/HEAD\" ]; then\n    source=\"`cut -d/ -f3 \"$git/HEAD\"`\"\n    if [ -n \"$source\" ]; then\n        if [ -f \"$git/refs/heads/$source\" ]; then\n            source=\"$source:`head -c 8 \"$git/refs/heads/$source\"`\"\n        else\n            source=\"${source%\"${source#????????}\"}\"\n        fi\n        source=\"~$source\"\n    fi\nfi\n\nVERSION=\"$1-%Y%m%d%H%M%S$source\"\n\nexec date \"+$VERSION\"\n"
  },
  {
    "path": "build/release.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\n# Generates a release in the releases/ directory (which should be a checkout of\n# the releases branch of the same repo) and pushes it.\n\nUSAGE=\"Usage: ${0##*/} [-f] bundle [..]\n    -f  Hard-reset the releases branch in case of unpushed releases.\"\nFORCE=''\n\n# CD into the repo's root directory\ndir=\"`readlink -f -- \"$0\"`\"\ncd \"${dir%/*}/..\"\n\n# Import common functions\n. installer/functions \n\nif [ \"$1\" = '-f' ]; then\n    FORCE=y\n    shift\nfi\n\nif [ \"$#\" = 0 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Check the releases directory and update, or create it if necessary\nif [ -d releases/.git ]; then\n    if ! grep -q 'releases$' releases/.git/HEAD; then\n        error 1 'releases/ is not in the releases branch.'\n    fi\n    if ! awk \"/'releases'/{print \\$1}\" releases/.git/FETCH_HEAD | \\\n            diff -q releases/.git/refs/heads/releases - >/dev/null; then\n        echo 'releases/ is ahead of or not up-to-date with remote' 1>&2\n        if [ -z \"$FORCE\" ]; then\n            exit 1\n        fi\n    fi\n    git -C releases fetch origin releases\n    git -C releases reset --hard origin/releases\nelif [ -e releases ]; then\n    error 1 \"releases/ is not a git repo\"\nelse\n    url=\"`git remote -v | awk '$1==\"origin\" && $3==\"(fetch)\" {print $2}'`\"\n    git clone --single-branch --branch releases --reference . \"$url\" releases\nfi\n\n# Apply the releases\nfor bundle in \"$@\"; do\n    bundle=\"${bundle##*/}\"\n    if [ ! -f \"$bundle\" ]; then\n        error 1 \"$bundle bundle does not exist\"\n    fi\n    version=\"`sh \"$bundle\" -V`\"\n    if [ \"$version\" = \"${version#crouton*:}\" ]; then\n        error 1 \"$bundle bundle is invalid\"\n    fi\n    branch=\"${version#*~}\"\n    branch=\"${branch%:*}\"\n    dest=\"${branch#master}\"\n    dest=\"crouton${dest:+-}$dest\"\n    # Compare the current release to avoid duplicates\n    if [ -f \"releases/$dest\" ] && \\\n            sh \"releases/$dest\" -V | grep -q \"${version#*~}\"; then\n        echo \"Release already current: `sh \"$bundle\" -V`\"\n        continue\n    fi\n    # Copy it in and make a commit\n    cp -fv \"$bundle\" \"releases/$dest\"\n    git -C releases add \"$dest\"\n    git -C releases commit -m \"$version\"\ndone\n\n# Push the resulting releases\ngit -C releases push origin releases\ngit -C releases fetch origin releases\n\nexit 0\n"
  },
  {
    "path": "build/wrapper.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file of the source repository, which has been replicated\n# below for convenience of distribution:\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are\n# met:\n#\n#    * Redistributions of source code must retain the above copyright\n# notice, this list of conditions and the following disclaimer.\n#    * Redistributions in binary form must reproduce the above\n# copyright notice, this list of conditions and the following disclaimer\n# in the documentation and/or other materials provided with the\n# distribution.\n#    * Neither the name of Google Inc. nor the names of its\n# contributors may be used to endorse or promote products derived from\n# this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n# This is a wrapped tarball. This script untars itself to a temporary directory\n# and then runs installer/main.sh with the parameters passed to it.\n# You can pass -x [directory] to extract the contents somewhere.\n\nset -e\n\nVERSION='git'\n\n# Minimum Chromium OS version is R45 stable\nCROS_MIN_VERS=7262\n\nif [ \"$1\" = '-x' -a \"$#\" -le 2 ]; then\n    # Extract to the specified directory.\n    SCRIPTDIR=\"${2:-\"${0##*/}.unbundled\"}\"\n    mkdir -p \"$SCRIPTDIR\"\nelse\n    # Make a temporary directory and auto-remove it when the script ends.\n    SCRIPTDIR='/tmp'\n    # If we're running from a directory in /tmp, make the temporary directory a\n    # subdirectory of it.\n    if [ \"${0#/tmp/}\" != \"$0\" ]; then\n        SCRIPTDIR=\"${0%/*}\"\n    fi\n    SCRIPTDIR=\"`mktemp -d --tmpdir=\"$SCRIPTDIR\" \"${0##*/}.XXX\"`\"\n    TRAP=\"rm -rf --one-file-system '$SCRIPTDIR';$TRAP\"\n    trap \"$TRAP\" INT HUP 0\nfi\n\n# Extract this file after the ### line\n# TARPARAMS will be set by the Makefile to match the compression method.\nline=\"`awk '/^###/ { print FNR+1; exit 0; }' \"$0\"`\"\ntail -n \"+$line\" \"$0\" | tar -x $TARPARAMS -C \"$SCRIPTDIR\"\n\n# Exit here if we're just extracting\nif [ -z \"$TRAP\" ]; then\n    exit\nfi\n\n# See if we want to just run a script from the bundle\nif [ \"$1\" = '-X' ]; then\n    script=\"$SCRIPTDIR/$2\"\n    if [ ! -f \"$script\" ]; then\n        cd \"$SCRIPTDIR\"\n        echo \"USAGE: ${0##*/} -X DIR/SCRIPT [ARGS]\nRuns a script directly from the bundle. Valid DIR/SCRIPT combos:\" 1>&2\n        ls chroot-bin/* host-bin/* 1>&2\n        if [ -n \"$2\" ]; then\n            echo 1>&2\n            echo \"Invalid script '$2'\" 1>&2\n        fi\n        exit 2\n    fi\n    shift 2\n    # If this script was called with '-x' or '-v', pass that on\n    SETOPTIONS=\"-e\"\n    if set -o | grep -q '^xtrace.*on$'; then\n        SETOPTIONS=\"$SETOPTIONS -x\"\n    fi\n    if set -o | grep -q '^verbose.*on$'; then\n        SETOPTIONS=\"$SETOPTIONS -v\"\n    fi\n    sh $SETOPTIONS \"$script\" \"$@\"\n    exit \"$?\"\nfi\n\n# Execute the main script inline. It will use SCRIPTDIR to find what it needs.\n. \"$SCRIPTDIR/installer/main.sh\"\n\nexit\n### end of script; tarball follows\n"
  },
  {
    "path": "chroot-bin/brightness",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# A shortcut script to control device brightness.\n# Usage:\n#    brightness [b|k] [up|down|# [instant]]\n\n# Choose the device\ndevice='Screen'\nnokbd=''\ncase \"$1\" in\nk*) device='Keyboard'\n    nokbd='echo \"Command not supported with keyboard backlight.\" 1>&2; exit 1' \n    shift;;\nb*) shift;;\nesac\n\n# Handle user command\nprint=''\npostcmd=''\ncase \"$1\" in\nu*) precmd='Increase';;\nd*) precmd='Decrease';;\n[0-9]*) eval $nokbd;\n    precmd='Set'; postcmd=\"Percent double:$1 int32:${2:-\"1\"}${2:+\"2\"}\";;\n*) eval $nokbd; precmd='Get'; postcmd='Percent'; print='--print-reply';;\nesac\n\nhost-dbus dbus-send --system --dest=org.chromium.PowerManager \\\n          --type=method_call $print /org/chromium/PowerManager \\\n          org.chromium.PowerManager.${precmd}${device}Brightness${postcmd} | {\n    read -r junk\n    read -r double percent\n    if [ -n \"$print\" ]; then\n        echo \"${percent%.*}\"\n    fi\n}\n\nexit 0\n"
  },
  {
    "path": "chroot-bin/crouton-noroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Wrapper for scripts that shouldn't be launched as root.\n# Symlink to launch the application with the same name in /usr/bin/\n# Launch directly with a parameter to launch that executable\n# Launch directly with a blank or - first parameter and a word to just check and\n# exit with an exit code, printing out the second parameter as the app name.\n\nAPPLICATION=\"${0##*/}\"\nif [ \"$APPLICATION\" = 'crouton-noroot' ]; then\n    if [ -z \"$1\" -o \"$1\" = '-' -o \"$1\" = '--' ]; then\n        APPLICATION=''\n    else\n        APPLICATION=\"$1\"\n        shift\n    fi\nelse\n    APPLICATION=\"/usr/bin/$APPLICATION\"\nfi\n\nif [ \"$USER\" = root -o \"$UID\" = 0 ]; then\n    app=\"${APPLICATION:-\"$2\"}\"\n    echo \"Do not launch ${app##*/} as root inside the chroot.\" 1>&2\n    exit 2\nelif [ -z \"$APPLICATION\" ]; then\n    exit 0\nfi\n\nexec \"$APPLICATION\" \"$@\"\n"
  },
  {
    "path": "chroot-bin/crouton-unity-autostart",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Helper script for launching services normally managed by upstart.\n# Called during session's XDG autostart.\n\nRELEASE=\"`/usr/local/bin/croutonversion -r`\"\n\n# Run the exec line from an Upstart conf file. Right now this assumes no space\n# in the path and no arguments.\nexecfromconf() {\n    local exec_cmd=\"`awk '/^exec/{print $2; exit}' \"$1\"`\"\n    if [ -x \"$exec_cmd\" ]; then\n        \"$exec_cmd\" &\n    fi\n}\n\n# Upstart launches unity on utopic+, and this needs to be started before\n# any dependent services.\nif [ \"$RELEASE\" = 'xenial' ]; then\n    PATH=\"$PATH\":/sbin /usr/bin/unity &\nfi\n\n# Launch window-stack-bridge for Unity HUD support, and unity-panel-service for\n# indicators.\nif [ \"$RELEASE\" != 'precise' ]; then\n    services=\"unity-panel-service window-stack-bridge\"\n    for service in $services; do\n        conf_file=/usr/share/upstart/sessions/\"$service\".conf\n        execfromconf \"$conf_file\"\n    done\nfi\n\n# If on trusty or later, indicators also need to be started.\nif [ \"$RELEASE\" = 'trusty' -o \"$RELEASE\" = 'xenial' ]; then\n    for conf_file in /usr/share/upstart/sessions/*.conf; do\n        if grep -q '^start on.* indicator-services-start' \"$conf_file\"; then\n            execfromconf \"$conf_file\"\n        fi\n    done\nfi\n"
  },
  {
    "path": "chroot-bin/croutonclip",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Synchronizes clipboard between X displays, making use of crouton's WebSocket\n# server and Chromium extension to synchronize the clipboard with Chromium OS\n\nVERBOSE=''\n\n. \"`dirname \"$0\"`/../installer/functions\"\n\n# rundisplay :X cmd ...\n# Run a command on the specified display\nrundisplay() {\n    local disp=\"$1\"\n    shift\n    DISPLAY=\"$disp\" \"$@\"\n}\n\ncopyclip() {\n    next=\"$1\"\n\n    # Do not copy if next is empty (display cannot be detected), or\n    # if current == next. Also set current=$next if $current is empty\n    if [ -z \"$next\" -o \"${current:=\"$next\"}\" = \"$next\" ]; then\n        if [ -n \"$VERBOSE\" ]; then\n            echo \"==Current: $current==Next: $next==\" 1>&2\n        fi\n        return 0\n    fi\n\n    if [ -n \"$VERBOSE\" ]; then\n        echo \">>Current: $current>>\" 1>&2\n    fi\n\n    # Copy clipboard content from the current display\n    {\n        if [ \"$current\" = 'cros' ]; then\n            echo -n 'R' | websocketcommand\n        else\n            # Check if display is still running\n            if rundisplay \"$current\" xdpyinfo >/dev/null 2>&1; then\n                echo -n 'R'\n                rundisplay \"$current\" xsel -ob\n            else\n                echo -n \"EUnable to open display '$current'.\"\n            fi\n        fi\n    } | (\n        STATUS=\"`head -c 1`\"\n        if [ \"$STATUS\" != 'R' ]; then\n            echo -n \"croutonwebsocket error: \" >&2\n            cat >&2\n            # Stop here (the clipboard content is lost in this case)\n            exit 0\n        fi\n\n        # Paste clipboard content to the next display\n        if [ \"$next\" = 'cros' ]; then\n            STATUS=\"`(echo -n 'W'; cat) | websocketcommand`\"\n            if [ \"$STATUS\" != 'WOK' ]; then\n                # Write failed, skip Chromium OS (do not update $current)\n                echo -n \"croutonwebsocket error: $STATUS\" >&2\n                exit 1\n            fi\n        else\n            # Do not override content if it \"looks\" the same\n            # (we might have rich text or other content in the clipboard)\n            cliptmp=\"`mktemp \"croutonclip.XXX\" --tmpdir=/tmp`\"\n            trap \"rm -f '$cliptmp'\" 0\n            cat > $cliptmp\n\n            if ! rundisplay \"$next\" xsel -ob \\\n                    | diff -q - \"$cliptmp\" > /dev/null; then\n                rundisplay \"$next\" xsel -ib < \"$cliptmp\"\n            fi\n        fi\n    ) && current=\"$next\"\n\n    if [ -n \"$VERBOSE\" ]; then\n        echo \"<<Next: $current<<\" 1>&2\n    fi\n}\n\n# Wait for the websocket server to get connected to the extension\n# Timeout after 10 seconds (twice crouton extension retry period)\nwaitwebsocket() {\n    timeout=10\n    while [ $timeout -gt 0 ]; do\n        if [ -n \"$VERBOSE\" ]; then\n            echo \"Ping...\" 1>&2\n        fi\n\n        # Prepare and send a ping message\n        MSG=\"PING$$$timeout\"\n        STATUS=\"`echo -n \"$MSG\" | websocketcommand`\"\n        if [ \"$STATUS\" = \"$MSG\" ]; then\n            if [ -n \"$VERBOSE\" ]; then\n                echo \"OK!\" 1>&2\n            fi\n            return 0\n        fi\n\n        if [ -n \"$VERBOSE\" ]; then\n            echo \"$STATUS\" 1>&2\n        fi\n\n        sleep 1\n        timeout=$(($timeout-1))\n    done\n    echo \"Timeout waiting for extension to connect.\" >&2\n}\n\n# Assume current display is Chromium OS: avoid race as we may not be able to\n# detect the first VT/window change.\ncurrent='cros'\n\nmkdir -m 775 -p \"$CROUTONLOCKDIR\"\nexec 3>>\"$CROUTONLOCKDIR/clip\"\nchmod -Rf g+rwX \"$CROUTONLOCKDIR\" || true\nchgrp -Rf crouton \"$CROUTONLOCKDIR\" || true\nif ! flock -n 3; then\n    echo \"Another instance of croutonclip running, waiting...\"\n    flock 3\nfi\n\naddtrap \"echo -n > '$CROUTONLOCKDIR/clip' 2>/dev/null\"\n\n(\n    # This subshell handles USR1 signals from croutoncycle.\n    # It prints a line when it receives a signal, or on VT change (we are able\n    # to filter out duplicate notifications).\n\n    # Start croutonwebsocket here to give \"wait\" something to wait for\n    croutonwebsocket &\n    addtrap \"kill $! 2>/dev/null\"\n\n    waitwebsocket\n\n    # Update on VT change (if user types Ctrl-Alt instead of Ctrl-Alt-Shift)\n    if hash croutonvtmonitor 2>/dev/null; then\n       croutonvtmonitor &\n       addtrap \"kill $! 2>/dev/null\"\n    fi\n\n    trap \"echo 'USR1'\" USR1\n\n    # Set the PID of this subshell after the trap is in place\n    sh -c 'echo -n \"$PPID\"' > \"$CROUTONLOCKDIR/clip\"\n\n    # Force an update when started.\n    echo \"Force\"\n\n    # Wait until all the children have terminated (gets interrupted on signal,\n    # which gives handlers a chance to run)\n    while ! wait; do\n        :\n    done\n) | (\n    # Do not hold the lock in this subshell and children (especially xsel)\n    exec 3>/dev/null\n    while read -r line; do\n        display=\"`croutoncycle display`\"\n        copyclip \"$display\"\n    done\n)\n\nexit 1\n"
  },
  {
    "path": "chroot-bin/croutoncycle",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n. \"$(dirname \"$0\")/../installer/functions\"\n\nUSAGE=\"${0##*/} next|prev|cros|list|#\nCycles through running graphical chroots.\n    next: switch to the next display\n    prev: switch to the previous display\n    cros: switch directly to Chromium OS\n    list: list all of the active displays and the associated chroot name\n    #: switch to the nth item in the list, zero-indexed\n    :#: switch directly to the chroot owning the specified display number\"\n\n# Undocumented:\n#   display: return display associated with current window\n#            (used from croutonclip):\n#     - cros: Chromium OS\n#     - :0: Chromium OS X11 display (non-aura window)\n#     - :1-9: chroot displays\n#   s<num>: informs of a Chromium OS window change (called from extension):\n#     - cros: any Chromium OS window\n#     - :1-9: kiwi window: X11 display number\n#   force [command]: Force switching display, even if it does not appear\n#                    to be necessary.\n\nforce=''\nif [ \"${1#[Ff]}\" != \"$1\" ]; then\n    force='y'\n    shift\nfi\n\ncase \"$1\" in\n[Ll]*) cmd='l';;\n[Dd]*) cmd='d';;\n[Cc]*) cmd='0';;\n[Pp]*) cmd='p';;\n[Nn]*) cmd='n';;\n[Ss]*) cmd='s' disp=\"${1#s}\";;\n:*) cmd=\"${1%%.*}\"; cmd=\":$((${cmd#:}))\";;\n[0-9]*) cmd=\"$(($1))\";;\n*) error 2 \"$USAGE\";;\nesac\n\n# Returns the chroot name of an X11 display specified in $1 on stdout\ngetname() {\n    local name='Unknown'\n    if [ \"$1\" = 'cros' ]; then\n        if [ -r '/var/host/lsb-release' ]; then\n            name=\"$(awk -F= '/_RELEASE_NAME=/{print $2}' \\\n                            '/var/host/lsb-release')\"\n        fi\n    else\n        local crname=\"$(DISPLAY=\":${1#:}\" xprop -root CROUTON_NAME 2>/dev/null)\"\n        if [ \"${crname%\\\"}\" != \"$crname\" ]; then\n            crname=\"${crname%\\\"}\"\n            name=\"${crname#*\\\"}\"\n        fi\n    fi\n    echo \"$name\"\n}\n\n# Only let one instance run at a time to avoid nasty race conditions\n# Make sure to release this before running websocket commands\nmkdir -m 775 -p \"$CROUTONLOCKDIR\"\nexec 3>\"$CROUTONLOCKDIR/cycle\"\nchmod -Rf g+rwX \"$CROUTONLOCKDIR\" || true\nchgrp -Rf crouton \"$CROUTONLOCKDIR\" || true\nflock 3\n\n# set display command from extension\nif [ \"$cmd\" = 's' ]; then\n    echo \"$disp\" > \"$CRIATDISPLAY\"\n    if [ -s \"$CROUTONLOCKDIR/clip\" ]; then\n        kill -USR1 \"$(cat \"$CROUTONLOCKDIR/clip\")\" || true\n    fi\n    exit 0\nfi\n\n# Ensure environment sanity\nexport XAUTHORITY=''\n\n# Set to y if there is any xiwi instance running\nxiwiactive=''\n\n# Prepare display list for easier looping\ndisplist='cros'\nfor disp in /tmp/.X*-lock; do\n    disp=\"${disp#*X}\"\n    disp=\":${disp%-lock}\"\n    # Only add VT-based and xiwi-based chroots here (that excludes Xephyr)\n    if [ \"$disp\" = ':0' ]; then\n        continue\n    elif DISPLAY=\"$disp\" xprop -root 'XFree86_VT' 2>/dev/null \\\n            | grep -q 'INTEGER'; then\n        displist=\"$displist $disp\"\n    elif DISPLAY=\"$disp\" xprop -root 'CROUTON_XMETHOD' 2>/dev/null \\\n            | grep -q '= \"xiwi'; then\n        displist=\"$displist $disp\"\n        xiwiactive='y'\n    fi\ndone\n\n# Set to the freon display owner if freon is used\nfreonowner=''\nif [ ! -f \"/sys/class/tty/tty0/active\" ]; then\n    if [ -f \"$CROUTONLOCKDIR/display\" ]; then\n        read -r freonowner < \"$CROUTONLOCKDIR/display\"\n    fi\n    freonowner=\"${freonowner:-0}\"\n    tty=''\nelse\n    tty=\"$(cat '/sys/class/tty/tty0/active')\"\nfi\n\n# Determine current display\nif [ \"$freonowner\" = 0 -o \"$tty\" = 'tty1' ]; then\n    # In Chromium OS or xiwi chroot\n    curdisp='cros'\n    if [ -n \"$xiwiactive\" -a -s \"$CRIATDISPLAY\" ]; then\n        kiwidisp=\"$(cat \"$CRIATDISPLAY\")\"\n        if [ \"${kiwidisp#:[0-9]}\" != \"$kiwidisp\" ]; then\n            curdisp=\"$kiwidisp\"\n        fi\n    fi\nelif [ -z \"$freonowner\" ]; then\n    # Poll the displays to figure out which one owns this VT\n    curdisp=\"$tty\"\n    for disp in $displist; do\n        if [ \"$disp\" = 'cros' ]; then\n            continue\n        fi\n        if DISPLAY=\"$disp\" xprop -root 'XFree86_VT' 2>/dev/null \\\n                | grep -q \" ${tty#tty}\\$\"; then\n            curdisp=\"$disp\"\n            break\n        fi\n    done\nelse\n    # Match the pid to the current freon owner\n    for lockfile in /tmp/.X*-lock; do\n        if grep -q \"\\\\<$freonowner$\" \"$lockfile\"; then\n            curdisp=\"${lockfile#*X}\"\n            curdisp=\":${curdisp%%-*}\"\n        fi\n    done\nfi\n\n# List the displays if requested\nif [ \"$cmd\" = 'l' -o \"$cmd\" = 'd' ]; then\n    for disp in $displist; do\n        active=' '\n        if [ \"$disp\" = \"$curdisp\" ]; then\n            active='*'\n            if [ \"$cmd\" = 'd' ]; then\n                echo \"$disp\"\n                exit 0\n            fi\n        fi\n\n        if [ \"$cmd\" = 'l' ]; then\n            echo -n \"$disp$active \"\n            getname \"$disp\"\n        fi\n    done\n    exit 0\nfi\n\n# Determine the target display\nif [ -n \"${cmd#[pn]}\" ]; then\n    if [ \"${cmd#:}\" != \"$cmd\" ]; then\n        destdisp=\"$cmd\"\n    else\n        i=0\n        destdisp=''\n        for disp in $displist; do\n            if [ \"$i\" -eq \"$cmd\" ]; then\n                destdisp=\"$disp\"\n                break\n            fi\n            i=\"$((i+1))\"\n        done\n        if [ -z \"$destdisp\" ]; then\n            error 2 \"Display number out of range.\"\n        fi\n    fi\nelif [ \"$cmd\" = 'p' ]; then\n    destdisp=\"${displist##* }\"\n    for disp in $displist; do\n        if [ \"$disp\" = \"$curdisp\" ]; then\n            break\n        fi\n        destdisp=\"$disp\"\n    done\nelif [ \"$cmd\" = 'n' ]; then\n    destdisp=''\n    for disp in $displist; do\n        if [ -n \"$destdisp\" ]; then\n            destdisp=\"$disp\"\n            break\n        elif [ \"$disp\" = \"$curdisp\" ]; then\n            destdisp=\"${displist%% *}\"\n        fi\n    done\n    if [ -z \"$destdisp\" ]; then\n        destdisp=\"${displist%% *}\"\n    fi\nelse\n    error 3 \"Bad command $cmd.\"\nfi\n\n# No-op on no-op\nif [ \"$destdisp\" = \"$curdisp\" -a -z \"$force\" ]; then\n    exit 0\nfi\n\n# Determine if the target display is on a VT\nif [ \"${destdisp#:}\" = \"$destdisp\" ]; then\n    if [ \"$destdisp\" != 'cros' ]; then\n        error 3 \"Bad destination display $destdisp.\"\n    fi\n    if [ -z \"$freonowner\" ]; then\n        export DISPLAY=\":0\"\n        export XAUTHORITY='/var/host/Xauthority'\n        if [ \"$tty\" != 'tty1' ]; then\n            sudo -n chvt 1\n            sleep .1\n        fi\n    elif [ \"${freonowner:-0}\" != 0 ]; then\n        kill -USR1 \"$freonowner\"\n    fi\n\n    if [ -n \"$xiwiactive\" ]; then\n        # Release the croutoncycle lock\n        exec 3>&-\n        STATUS=\"$(echo -n \"Xcros\" | websocketcommand)\"\n        if [ \"$STATUS\" != 'XOK' ]; then\n            error 1 \"${STATUS#?}\"\n        fi\n    fi\nelse\n    export DISPLAY=\"$destdisp\"\n    xmethod=\"$(xprop -root 'CROUTON_XMETHOD' 2>/dev/null \\\n               | sed -n 's/^.*\\\"\\(.*\\)\\\"/\\1/p')\"\n    if [ \"${xmethod%%-*}\" = 'xiwi' ]; then\n        if [ -z \"$freonowner\" -a \"$tty\" != 'tty1' ]; then\n            sudo -n chvt 1\n            sleep .1\n        elif [ \"${freonowner:-0}\" != 0 ]; then\n            kill -USR1 \"$freonowner\"\n        fi\n        # Release the croutoncycle lock\n        exec 3>&-\n        STATUS=\"$(echo -n \"X${destdisp} ${xmethod#*-}\" | websocketcommand)\"\n        if [ \"$STATUS\" != 'XOK' ]; then\n            error 1 \"${STATUS#?}\"\n        fi\n    elif [ -z \"$freonowner\" ]; then\n        dest=\"$(xprop -root 'XFree86_VT' 2>/dev/null)\"\n        dest=\"${dest##* }\"\n        if [ \"${dest#[1-9]}\" = \"$dest\" ]; then\n            dest='1'\n        fi\n        # When the destination we are changing to is using fbdev driver in X, we\n        # need first a change to vt 2, else only the session switches and the\n        # display will be stuck on the old vt.\n        sudo -n chvt 2\n        sudo -n chvt \"$dest\"\n    else\n        dest=\"/tmp/.X${destdisp#:}-lock\"\n        if [ -f \"$dest\" ]; then\n            # Trigger the target before releasing the current owner\n            kill -USR1 \"$(cat \"/tmp/.X${destdisp#:}-lock\")\"\n        fi\n        if [ \"${freonowner:-0}\" != 0 ]; then\n            kill -USR1 \"$freonowner\"\n        fi\n    fi\nfi\n\nif [ -s \"$CROUTONLOCKDIR/clip\" ]; then\n    kill -USR1 \"$(cat \"$CROUTONLOCKDIR/clip\")\" || true\nfi\n\n# Wait a flip and then refresh the display for good measure\nif [ -n \"$DISPLAY\" ] && hash xrefresh 2>/dev/null; then\n    sleep .1\n    xrefresh\nfi\n\nexit 0\n"
  },
  {
    "path": "chroot-bin/croutonfindnacl",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# croutonfindnacl address signature [\"pids\"]\n#\n# This script is used by croutonfbserver to find the nacl_helper process it is\n# connected to, and, in particular, the file descriptor corresponding to the shm\n# memory that the nacl_helper process shares with Chromium.\n#\n# - address:   NaCl-space address of the shared memory (hexadecimal). We assume\n#              that the NaCl/hardware memory mapping conserves the address,\n#              possibly with a prefix in the MSBs.\n# - signature: random 8 byte pattern (hexadecimal, machine byte order) that is\n#              written at the beginning of the shared buffer by the NaCl\n#              application. The first 8 bytes of each candidate buffer is read,\n#              guaranteeing that the correct buffer is returned.\n# - pids:      (normally ununsed, defaults to all processes named \"nacl_helper\")\n#              Space-separated list of PIDs to scan for.\n#\n# On success, prints \"pid:filename\" and exits with code 0.\n# On error (shm not found, invalid parameters), exits with code >0.\n\nset -e\n\nVERBOSE=\n\nif [ \"$#\" -lt 2 -o \"$#\" -gt 3 ]; then\n    echo \"Invalid parameters\"\n    exit 2\nfi\n\nADDRESS=\"$1\"\nPATTERN=\"$2\"\nPIDS=\"${3:-\"`pgrep nacl_helper`\"}\"\n\nMATCH=\"\"\n\n# Iterate over all NaCl helper processes\nfor pid in $PIDS; do\n    [ -n \"$VERBOSE\" ] && echo \"pid:$pid\" 1>&2\n    # Find candidate mappings\n    file=\"`awk '$1 ~ /^[0-9a-f]*'\"$ADDRESS\"'-/ && $2 == \"rw-s\" \\\n             && $6 ~ /\\/shm\\/\\.(com\\.google\\.Chrome|org\\.chromium\\.Chromium)/ \\\n                   { print $6 }\n    ' \"/proc/$pid/maps\"`\"\n    [ -n \"$VERBOSE\" ] && echo \"file:$file\" 1>&2\n    if [ -z \"$file\" ]; then\n        continue\n    fi\n\n    # Iterate over mappings, and check signature\n    for fd in \"/proc/$pid/fd\"/*; do\n        link=\"$(readlink -- \"$fd\" || true)\"\n        link=\"${link% (deleted)}\"\n        if [ \"$link\" = \"$file\" ]; then\n            # Check if signature matches\n            pattern=\"`od -An -t x1 -N8 \"$fd\" | tr -d ' '`\"\n            [ -n \"$VERBOSE\" ] && echo \"FD:$fd ($pattern)\" 1>&2\n            if [ \"$pattern\" = \"$PATTERN\" ]; then\n                # Second match? This should never happen\n                if [ -n \"$MATCH\" ]; then\n                    echo -n \"-1:ambiguous\"\n                    exit 1\n                fi\n                MATCH=\"$pid:$fd\"\n            fi\n        fi\n    done\ndone\n\nif [ -n \"$MATCH\" ]; then\n    echo -n \"$MATCH\"\n    exit 0\nelse\n    echo -n \"-1:no match\"\n    exit 1\nfi\n"
  },
  {
    "path": "chroot-bin/croutonnotify",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nUSAGE=\"${0##*/} -j\n${0##*/} -t title [-m message] [-d] [-i icon] [-I id]\nRaises a notification in Chromium OS (requires crouton extension).\n\nWhen -j is specified, reads JSON data from stdin, in the format required\nby chrome.notifications API, with 2 additional fields, \\\"crouton_id\\\" and\n\\\"crouton_display\\\", corresponding respectively to the notification id, and the\ndisplay to switch to when the notification is clicked (croutoncycle parameter).\n\nOtherwise, constructs a \\\"basic\\\" notification with the requested fields.\n\nOptions:\n    -j       Read JSON data from stdin (see example below)\n    -t       Title to display (chrome.notifications field: \\\"title\\\")\n    -m       Message to display (chrome.notifications field: \\\"message\\\")\n    -d       Switch to display in environment variable DISPLAY ($DISPLAY) when\n             the notification is clicked. Default: Do not switch display.\n    -i       Small icon to display on the left of the notification\n             (chrome.notifications field: \\\"iconUrl\\\")\n    -I       ID to pass to chrome.notifications.create: only the last\n             notification with a given ID is shown.\n             Default: Display a new notification.\n\nExample JSON data:\n\"'{\n  \"type\": \"basic\",\n  \"title\": \"Primary Title\",\n  \"message\": \"Primary message to display\",\n  \"crouton_display\": \":1\",\n  \"iconUrl\": \"data:image/png;base64,<base64 encoded png data>\"\n}'\n\n. \"$(dirname \"$0\")/../installer/functions\"\n\nSWITCHDISPLAY=\"\"\nMESSAGE=\"\"\nTITLE=\"\"\nID=\"\"\nICON=\"\"\nJSON=\"\"\n\n# Process arguments\nwhile getopts 'di:I:jm:t:' f; do\n    case \"$f\" in\n    d) SWITCHDISPLAY=\"$DISPLAY\";;\n    i) ICON=\"$OPTARG\";;\n    I) ID=\"$OPTARG\";;\n    j) JSON=\"y\";;\n    m) MESSAGE=\"$OPTARG\";;\n    t) TITLE=\"$OPTARG\";;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\n\n# No extra parameters, -j precludes other parameters, and at least title\n# must be specified (or -j)\nif [ \"$#\" != \"$((OPTIND-1))\" ] ||\n        [ \"$JSON\" = 'y' -a -n \"$SWITCHDISPLAY$ICON$ID$MESSAGE$TITLE\" ] ||\n        [ -z \"$JSON$TITLE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\nif [ -n \"$ICON\" ] && [ ! -r \"$ICON\" -o ! -f \"$ICON\" ]; then\n    error 2 \"Cannot open $ICON.\"\nfi\n\n# Escape json string (backslash and double quotes)\njson_escape() {\n    echo -n \"$1\" | sed -e 's/\\\\/\\\\u005C/g;s/\"/\\\\u0022/g;2~1s/^/\\\\u000A/;' \\\n                 | tr -d '\\n'\n}\n\nSTATUS=\"$({\n    echo -n \"N\"\n    if [ -z \"$JSON\" ]; then\n        echo -n '{\n            \"type\": \"basic\",\n            \"title\": \"'\"$(json_escape \"$TITLE\")\"'\",\n            \"message\": \"'\"$(json_escape \"$MESSAGE\")\"'\",\n            \"crouton_display\": \"'\"$(json_escape \"$SWITCHDISPLAY\")\"'\",\n            \"crouton_id\": \"'\"$(json_escape \"$ID\")\"'\"'\n        if [ -n \"$ICON\" ]; then\n            ext=\"${ICON##*.}\"\n            if grep -Iq '<svg' \"$ICON\"; then\n                ext='svg+xml'\n            fi\n            echo -n ', \"iconUrl\": \"data:image/'\"${ext:-png}\"';base64,'\n            base64 -w 0 \"$ICON\"\n            echo '\"'\n        fi\n        echo -n '}'\n    else\n        cat\n    fi\n} | websocketcommand)\"\n\nif [ \"$STATUS\" != 'NOK' ]; then\n    error 1 \"${STATUS#?}\"\nfi\n"
  },
  {
    "path": "chroot-bin/croutonpowerd",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nUSAGE=\"${0##*/} -p|--poke|-deactivate|--daemon\n${0##*/} -i|--inhibit [command [params ...]]\nInterfaces with Chromium OS's powerd to prevent the system from suspending.\n\nPasses parameters to the appropriate command if called as xscreensaver-command\nor gnome-screensaver-command.\n\nOptions:\n    -i --inhibit           Inhibits powerd for the duration of the command, or\n                           forever if no command is specified.\n    -p --poke -deactivate  Pings powerd to prevent activation.\n    --suspend              Switches to Chromium OS and triggers a suspend.\n    --daemon               Enters daemon mode where idleness and screensaver\n                           inhibition are passed on to Chromium OS's powerd.\"\n\nINHIBITSLEEP='30'\nDAEMONSLEEP='15'\n\n# Common functions\n. \"`dirname \"$0\"`/../installer/functions\"\n\n# Check if we need to pass through to another command\nEXEC=\"${0##*/}\"\ncase \"$EXEC\" in\ngnome-screensaver-command|xscreensaver-command) EXEC=\"/usr/bin/$EXEC\";;\n*) EXEC='';;\nesac\n\n# Check what command we need to run, or immediately pass through if unrecognized\ncase \"$1\" in\n-i|--inhibit) CMD='i';;\n-p|--poke|-deactivate) CMD='p';;\n--suspend) CMD='s';;\n--daemon) CMD='d';;\n*)  if [ -z \"$EXEC\" ]; then\n        echo \"$USAGE\" 1>&2\n        exit 2\n    fi\n    exec \"$EXEC\" \"$@\";;\nesac\n\nhostdbus=''\nif hash host-dbus 2>/dev/null; then\n    hostdbus='host-dbus'\nfi\n\npingpowerd() {\n    $hostdbus dbus-send --system --dest=org.chromium.PowerManager \\\n                        --type=method_call /org/chromium/PowerManager \\\n                        org.chromium.PowerManager.HandleUserActivity \\\n                        int32:0 || true\n}\n\nif [ \"$CMD\" = 'p' ]; then\n    # Ping\n    pingpowerd\n    exec \"${EXEC:-\"true\"}\" \"$@\"\nelif [ \"$CMD\" = 'i' ]; then\n    # Inhibit\n    while pingpowerd; do\n        sleep \"$INHIBITSLEEP\"\n    done &\n    pid=$!\n    addtrap \"kill '$pid' 2>/dev/null\"\n    if [ -n \"$EXEC\" ]; then\n        \"$EXEC\" \"$@\"\n    elif [ -n \"$2\" ]; then\n        shift\n        \"$@\"\n    else\n        wait $pid\n    fi\nelif [ -n \"$EXEC\" ]; then\n    exec \"$EXEC\" \"$@\"\nelif [ \"$CMD\" = 's' ]; then\n    if hash croutoncycle 2>/dev/null; then\n        croutoncycle cros\n    fi\n    $hostdbus dbus-send --system --dest=org.chromium.PowerManager \\\n                        --type=method_call /org/chromium/PowerManager \\\n                        org.chromium.PowerManager.RequestSuspend || true\nelif [ \"$CMD\" = 'd' ]; then\n    if [ -z \"$DISPLAY\" ]; then\n        error 1 'Cannot launch daemon: $DISPLAY not specified.'\n    fi\n\n    # Daemon\n    xdgs='/usr/bin/xdg-screensaver'\n    xi2pid=''\n\n    # Send pings to powerd at regular intervals, if the user is active (i.e.\n    # there are input events), or if the screensaver is disabled.\n    # For performance reason, we probably do not want to monitor every single\n    # X input events. Therefore, we start a subshell that pings powerd upon\n    # receiving a single event, then quits.\n    # Every $DAEMONSLEEP seconds we check if that subshell if alive, and\n    # restart it if necessary. This means that we miss events during up to \n    # $DAEMONSLEEP seconds, and, if powerd timeout is X seconds, then the\n    # screen may already dim after X-DAEMONSLEEP seconds of inactivity. This is\n    # not noticeable if DAEMONSLEEP is much smaller than X (usually, X=300s).\n\n    while sleep \"$DAEMONSLEEP\"; do\n        if [ \"`\"$xdgs\" status 2>/dev/null`\" = 'disabled' ]; then\n            # Screensaver disabled: ping\n            pingpowerd\n        elif [ -z \"$xi2pid\" ] || ! kill -0 \"$xi2pid\" 2>/dev/null; then\n            # croutonxi2event subshell is not running\n\n            if [ -n \"$xi2pid\" ]; then\n                # Fail if return status from process != 0: wait returns the exit\n                # status of the child, and this shell exits on error (-e)\n                wait $xi2pid\n            fi\n\n            # Wait for input event, then ping immediately\n            ( croutonxi2event -1 >/dev/null 2>&1 && pingpowerd ) &\n            xi2pid=$!\n        fi\n    done\nfi\n\nexit 0\n"
  },
  {
    "path": "chroot-bin/croutontriggerd",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Monitors keyboard events for the crouton switch command.\n\n. \"`dirname \"$0\"`/../installer/functions\"\n\n# hexdump output format variables\nif getconf LONG_BIT | grep -q 32; then\n    HEXDUMP_FMT='2/4 \"%u 0 \" \" \" 2/2 \"%u \" \" \" 1/4 \"%u \" \"\\n\"'\nelse\n    HEXDUMP_FMT='4/4 \"%u \" \" \" 2/2 \"%u \" \" \" 1/4 \"%u \" \"\\n\"'\nfi\nSECONDS_LO='$1'\nSECONDS_HI='$2'\nUSECONDS_LO='$3'\nUSECONDS_HI='$4'\nTYPE='$5'\nKEY='$6'\nSTATE='$7'\n\n# constants\nTYPE_EV_KEY=1\nSTATE_DOWN=1\nSTATE_UP=0\nKEY_LEFTCTRL=29\nKEY_LEFTALT=56\nKEY_LEFTSHIFT=42\nKEY_RIGHTCTRL=97\nKEY_RIGHTALT=100\nKEY_RIGHTSHIFT=54\nKEY_F1=59\nKEY_F2=60\n\nEVENT_DEV_POLL=15\n\n# Only one at a time. xbindkeys lockfile is for legacy compatibility\nmkdir -m 775 -p \"$CROUTONLOCKDIR\"\nexec 3>>\"$CROUTONLOCKDIR/xbindkeys\"\nchmod -Rf g+rwX \"$CROUTONLOCKDIR\" || true\nchgrp -Rf crouton \"$CROUTONLOCKDIR\" || true\nif ! flock -n 3; then\n    echo \"Another instance of ${0##*/} running, waiting...\"\n    flock 3\nfi\n\n# Reset event variables to handle strange environments\nunset `set | grep -o '^event[0-9]*'` 2>/dev/null || true\n\n# Poll for new event files and dump the output\nwhile :; do\n    # Clean up old hexdumps and start new ones\n    for event in `set | grep -o '^event[0-9]*'` /dev/input/event*; do\n        # Check if the event file is already monitored\n        eval \"pid=\\\"\\${${event##*/}:-0}\\\"\"\n        if [ \"$pid\" != 0 ]; then\n            # Check if it's still running\n            if kill -0 \"$pid\" 2>/dev/null; then\n                continue\n            fi\n            wait \"$pid\" || true\n        fi\n        # Clean up old variables\n        if [ \"${event#/}\" = \"$event\" ]; then\n            unset \"$event\"\n        else\n            # Read in the event files and split into input_event fields\n            stdbuf -oL hexdump -e \"$HEXDUMP_FMT\" \"$event\" &\n            eval \"${event##*/}='$!'\"\n        fi\n    done\n    # Avoid picking up the event variable\n    unset event\n    # Kill all event daemons\n    pids=\"`set | sed -n 's/^event[0-9]*=.\\(.*\\).$/\\1/p' | tr '\\n' ' '`\"\n    settrap \"kill $pids 2>/dev/null;\"\n    # Wait for next poll\n    sleep \"$EVENT_DEV_POLL\"\ndone | unbuffered_awk \"\n    function update() {\n        c = lc || rc; s = ls || rs; a = la || ra\n        if (!cmd && c && s && a && p) {\n            cmd = \\\"p\\\"\n        } else if (!cmd && c && s && a && n) {\n            cmd = \\\"n\\\"\n        } else if (cmd && !c && !s && !a && !p && !n) {\n            system(\\\"/usr/local/bin/croutoncycle \\\" cmd)\n            cmd = \\\"\\\"\n        }\n    }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_LEFTCTRL   { lc = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_LEFTSHIFT  { ls = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_LEFTALT    { la = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_RIGHTCTRL  { rc = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_RIGHTSHIFT { rs = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_RIGHTALT   { ra = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_F1         {  p = $STATE; update() }\n    $TYPE == $TYPE_EV_KEY && $KEY == $KEY_F2         {  n = $STATE; update() }\n\"\n"
  },
  {
    "path": "chroot-bin/croutonurlhandler",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nNEWTABPAGE='chrome://newtab'\nUSAGE=\"${0##*/} [-n] [URL]\nOpen an URL in Chromium OS (requires crouton extension).\nIf no URL is specified, opens $NEWTABPAGE instead.\nSwitches back to Chromium OS unless -n is specified.\"\n\n. \"`dirname \"$0\"`/../installer/functions\"\n\nnoswitch=''\nif [ \"$1\" = '-n' ]; then\n    noswitch='y'\n    shift\nfi\n\nif [ -z \"$*\" ]; then\n    set -- \"$NEWTABPAGE\"\nelif [ \"$1\" = '-h' -o \"$1\" = '--help' ]; then\n    error 0 \"$USAGE\"\nfi\n\nSTATUS=\"`echo -n U\"$*\" | websocketcommand`\"\n\nif [ \"$STATUS\" != 'UOK' ]; then\n    error 1 \"${STATUS#?}\"\nfi\n\nif [ -z \"$noswitch\" ]; then\n    croutoncycle cros\nfi\n"
  },
  {
    "path": "chroot-bin/croutonversion",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nVERSION='unknown'\nRELEASE='unknown'\nARCH='unknown'\n\nAPPLICATION=\"${0##*/}\"\nCHANGES=''\nDOWNLOAD=''\nUPDATES=''\n\nBRANCH=\"${VERSION##*~}\"\nBRANCH=\"${BRANCH%%:*}\"\nCOMMIT=\"${VERSION##*:}\"\nCHANGESURL=\"https://github.com/dnschneid/crouton/compare/$COMMIT...\"\nINSTALLER=\"crouton-$BRANCH\"\nINSTALLER=\"${INSTALLER%-master}\"\nCROUTONURL=\"https://github.com/dnschneid/crouton/raw/releases/$INSTALLER\"\nDEST=\"$HOME/Downloads/$INSTALLER\"\n\nUSAGE=\"$APPLICATION [options]\n\nReports the version of crouton installed, checks if updates are available,\nand/or launches a changelog.\n\nIf no options are specified, outputs crouton version information to STDOUT.\n\nOptions:\n    -a       Prints the architecture of this chroot to stdout.\n    -c       Launches a browser to view the list of changes.\n             If -u is specified, only launches if a newer version exists.\n    -d       Downloads the latest version of crouton to the location specified.\n             If -u is specified, only downloads if the version is newer.\n    -f FILE  Changes the destination of the downloaded crouton.\n             Default: $DEST\n    -h       Prints out usage.\n    -r       Prints the release of this chroot to stdout.\n    -u       Checks for updates, and prints out the updated version number.\"\n\n\n# Function to exit with exit code $1, spitting out message $@ to stderr\nerror() {\n    local ecode=\"$1\"\n    shift\n    echo \"$*\" 1>&2\n    exit \"$ecode\"\n}\n\n# Process arguments\nwhile getopts 'acdf:ru' f; do\n    case \"$f\" in\n    a) echo \"$ARCH\"; exit 0;;\n    c) CHANGES='y';;\n    d) DOWNLOAD='y';;\n    f) DEST=\"$OPTARG\";;\n    r) echo \"$RELEASE\"; exit 0;;\n    u) UPDATES='y';;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\n\n# No extra parameters\nif [ \"$#\" != \"$((OPTIND-1))\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Print out version if nothing else specified\nif [ -z \"$CHANGES$DOWNLOAD$UPDATES\" ]; then\n    echo \"crouton: version $VERSION\"\n    echo \"release: $RELEASE\"\n    echo \"architecture: $ARCH\"\n    xmethodfile='/etc/crouton/xmethod'\n    if [ -r \"$xmethodfile\" ]; then\n        echo \"xmethod: `cat \"$xmethodfile\"`\"\n    fi\n    targetfile='/etc/crouton/targets'\n    if [ -r \"$targetfile\" ]; then\n        echo \"targets: `sed 's/^,//' \"$targetfile\"`\"\n    fi\n    hostrel='/var/host/lsb-release'\n    if [ -r \"$hostrel\" ]; then\n        host=\"`awk -F= '/_RELEASE_DESCRIPTION=/{print $2}' \"$hostrel\"`\"\n    fi\n    echo \"host: version ${host:-unknown}\"\n    echo \"kernel: $(uname -a)\"\n    freon=\"yes\"\n    if [ -f /sys/class/tty/tty0/active ]; then\n        freon=\"no\"\n    fi\n    echo \"freon: $freon\"\n    exit 0\nfi\n\n# Print out version to stderr for info\necho \"crouton: version $VERSION\" 1>&2\n\nlatest=''\ntmpdir=''\nif [ -n \"$UPDATES$DOWNLOAD\" ]; then\n    tmpdir=\"`mktemp -d --tmpdir=/tmp crouton.XXX`\"\n    trap \"rm -rf --one-file-system '$tmpdir'\" INT HUP 0\n    echo \"Retrieving latest version of $INSTALLER\" 1>&2\n    if ! wget \"$CROUTONURL\" -O \"$tmpdir/$INSTALLER\" 2>\"$tmpdir/log\"; then\n        cat \"$tmpdir/log\" 1>&2\n        echo \"Failed to retrieve latest version of $INSTALLER\" 1>&2\n        exit 1\n    fi\n    latest=\"`awk -F\"'\" '/^VERSION=/{print $2;exit}' \"$tmpdir/$INSTALLER\"`\"\nfi\n\n# Print out latest version number if requested\nif [ -n \"$UPDATES\" ]; then\n    echo \"latest: version $latest\"\nelif [ -n \"$latest\" ]; then\n    echo \"latest: version $latest\" 1>&2\nfi\n\n# Save latest version if requested\nif [ -n \"$DOWNLOAD\" -a \"$VERSION\" != \"$latest\" ]; then\n    echo \"Saving latest version to $DEST\" 1>&2\n    mv -f \"$tmpdir/$INSTALLER\" \"$DEST\"\nfi\n\n# Launch changelog if requested\nif [ -n \"$CHANGES\" -a \"$VERSION\" != \"$latest\" ]; then\n    # Check if changelogs are available from this version / to this branch\n    if ! wget \"$CHANGESURL$BRANCH\" -O/dev/full 2>&1 | grep -q '404 Not Found'; then\n        CHANGESURL=\"$CHANGESURL$BRANCH\"\n    elif ! wget \"${CHANGESURL}master\" -O/dev/full 2>&1 | grep -q '404 Not Found'; then\n        CHANGESURL=\"${CHANGESURL}master\"\n    else\n        # Fall back on the main commit log\n        CHANGESURL='https://github.com/dnschneid/crouton/commits/'\n    fi\n    # One of these will probably work...\n    for x in exo-open gnome-open kde-open xdg-open \\\n             sensible-browser x-www-browser www-browser; do\n        if hash \"$x\" 2>/dev/null; then\n            browser=\"$x\"\n            break\n        fi\n    done\n    if [ -z \"$browser\" ]; then\n        error 2 \"No browser found to view $CHANGESURL\"\n    fi\n    # Launch the webpage\n    if ! \"$browser\" \"$CHANGESURL\"; then\n        error 1 \"Failed to launch browser to view $CHANGESURL\"\n    fi\nfi\n\nexit 0\n"
  },
  {
    "path": "chroot-bin/croutonxinitrc-wrapper",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# xinitrc wrapper for crouton:\n#  1. Runs crouton-specific commands\n#  2. Runs the provided client (emulating xinit behaviour)\n#  3. Runs crouton-specific commands before the server is destroyed\n\ncmd=''\nextraargs=''\nbinary=''\nret=0\n\n# This part is a translation of what is found in xorg's xinit.c\n\nif [ -z \"$1\" ] || [ \"${1#[/.]}\" = \"$1\" ]; then\n    # No client parameter: find .xinitrc if possible, run xterm otherwise\n    required=''\n\n    if [ -n \"$XINITRC\" ]; then\n        cmd=\"$XINITRC\"\n        required='y'\n    elif [ -n \"$HOME\" ]; then\n        cmd=\"$HOME/.xinitrc\"\n    fi\n\n    if [ ! -e \"$cmd\" ]; then\n        if [ -n \"$required\" ]; then\n            echo \"Warning, no client init file \\\"$cmd\\\"\" 1>&2\n        fi\n\n        # If no client is given, use default command\n        cmd=\"xterm\"\n        extraargs=\"-geometry +1+1 -n login\"\n        # Make sure xterm is executed directly: let sh resolve the path\n        binary='y'\n    fi\nelse\n    cmd=\"$1\"\n    shift\nfi\n\n# Run crouton-specific commands:\n\n# Show chroot specifics for troubleshooting\ncroutonversion 1>&2\n\nif [ -z \"$XMETHOD\" ]; then\n    if [ -f '/etc/crouton/xmethod' ]; then\n        read -r XMETHOD _ < /etc/crouton/xmethod\n        export XMETHOD\n    else\n        echo 'X11 backend not set.' 1>&2\n        exit 1\n    fi\nfi\nxmethodtype=\"${XMETHOD%%-*}\"\nxmethodargs=\"${XMETHOD#*-}\"\n\n# Record the name of the chroot in the root window properties\nif [ -f '/etc/crouton/name' ] && hash xprop 2>/dev/null; then\n    xprop -root -f CROUTON_NAME 8s -set CROUTON_NAME \"`cat '/etc/crouton/name'`\"\nfi\n\n# Record the crouton XMETHOD in the root window properties\nxprop -root -f CROUTON_XMETHOD 8s -set CROUTON_XMETHOD \"$XMETHOD\"\n\n# Launch the powerd poker daemon\ncroutonpowerd --daemon &\n\n# Launch the clipboard synchronization daemon\nif hash croutonclip 2>/dev/null; then\n    croutonclip &\nfi\n\n# Launch system-wide trigger daemon\ncroutontriggerd &\n\n\n# Apply the Chromebook keyboard map. Not needed for non-Freon xiwi.\nif [ \"$xmethodtype\" != 'xiwi' -o ! -f \"/sys/class/tty/tty0/active\" ]; then\n    # Apply the Chromebook keyboard map if installed.\n    if [ -f '/usr/share/X11/xkb/compat/chromebook' ]; then\n        setxkbmap -model chromebook\n    fi\nfi\n\n# Input-related stuff is not needed for kiwi\nif [ \"$xmethodtype\" != \"xiwi\" ]; then\n\n    # Launch X-server-local key binding daemon\n    xbindkeys -fg /etc/crouton/xbindkeysrc.scm\n\n    # Launch touchegg if it is requested.\n    toucheggconf='/etc/touchegg.conf'\n    if [ -f \"$toucheggconf\" ]; then\n        mkdir -p \"$HOME/.config/touchegg\"\n        ln -sf \"$toucheggconf\" \"$HOME/.config/touchegg/\"\n        touchegg 2>/dev/null &\n    fi\n\n    # Configure trackpad settings if needed\n    if synclient >/dev/null 2>&1; then\n        # Elan trackpads usually like these settings\n        if grep -q 'Elan Touchpad' /sys/class/input/event*/device/name; then\n            SYNCLIENT=\"FingerLow=1 FingerHigh=5 $SYNCLIENT\"\n        fi\n        # Other special cases\n        case \"`awk -F= '/_RELEASE_BOARD=/{print $2}' '/var/host/lsb-release'`\" in\n            butterfly*|eve*|falco*)\n                SYNCLIENT=\"FingerLow=1 FingerHigh=5 $SYNCLIENT\";;\n            parrot*|peppy*|wolf*)\n                SYNCLIENT=\"FingerLow=5 FingerHigh=10 $SYNCLIENT\";;\n        esac\n        if [ -n \"$SYNCLIENT\" ]; then\n            synclient $SYNCLIENT\n        fi\n    fi\nfi\n\n# Crouton-in-a-tab: Start fbserver and launch display\nif [ \"$xmethodtype\" = 'xiwi' ]; then\n    # The extension sends evdev key codes: fix the keyboard mapping rules\n    setxkbmap -rules evdev\n    # Reapply xkb map: This fixes autorepeat mask in \"xset q\"\n    xkbcomp \"$DISPLAY\" - | xkbcomp - \"$DISPLAY\" 2>/dev/null\n\n    # Set resolution to a default 1024x768, this is important so that the DPI\n    # looks reasonable when the WM/DE start.\n    setres 1024 768 > /dev/null\n    croutonfbserver \"$DISPLAY\" &\n\n    try=1\n    while ! croutoncycle force \"$DISPLAY\"; do\n        echo \"Cannot connect to extension, retrying...\"\n        if [ \"$try\" -ge 10 ]; then\n            echo \"\\\nUnable to start display, make sure the crouton extension is installed\nand enabled, and up to date. Download from:\n  https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom\" 1>&2\n            ret=1\n            break\n        fi\n        sleep 1\n        try=\"$((try+1))\"\n    done\n    if [ \"$ret\" -eq 0 ]; then\n        echo \"Connected to extension, launched crouton in a window.\" 1>&2\n    fi\nfi\n\nif [ \"$xmethodtype\" = \"xorg\" ]; then\n    # Since Chromium 56.0.2923.0, Chromium tries to switch off the display when\n    # switching VT (crbug.com/655770). For some unclear reason, running xrandr\n    # forces the display to be back on, and this is not needed ever again\n    # when switching VTs.\n    # The loop tries to work around a race that is more likely on xenial\n    try=1\n    while xrandr --auto 2>&1 | grep . 1>&2; do\n        echo \"Kicking xrandr again\" 1>&2\n        if [ \"$try\" -ge 10 ]; then\n            break\n        fi\n        sleep 1\n        try=\"$((try+1))\"\n    done\nfi\n\n# Only run if no error occured before (e.g. cannot connect to extension)\nif [ \"$ret\" -eq 0 ]; then\n    # Shell is the leader of a process group, so signals sent to this process\n    # are propagated to its children. We ignore signals in this process, but the\n    # child handles them and exits. We use a no-op handler, as \"\" causes the\n    # signal to be ignored in children as well (see NOTES in \"man 2 sigaction\"\n    # for details). This process then runs exit commands, and terminates.\n    trap \"true\" HUP INT TERM\n\n    # Run the client itself if it is executable, otherwise run it in a shell.\n    if [ -n \"$binary\" -o -x \"$cmd\" ]; then\n        \"$cmd\" $extraargs \"$@\" || ret=$?\n    else\n        /bin/sh \"$cmd\" $extraargs \"$@\" || ret=$?\n    fi\n\n    trap - HUP INT TERM\nfi\n\n# Run crouton-specific commands before the server exits:\n\necho \"Running exit commands...\" 1>&2\n\nexit \"$ret\"\n"
  },
  {
    "path": "chroot-bin/gnome-session-wrapper",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Provides a wrapper around gnome-session to allow gnome-session based\n# desktop environments to work with crouton nicely\n\nUSAGE=\"${0##*/} [session]\nA wrapper around gnome-session that can be passed to xinit.\nProvide the session type in the DESKTOP_SESSION variable, \nor as an optional session argument.\n\nExamples:\n(launch GNOME from crosh with session in DESKTOP_SESSION)\nDESKTOP_SESSION=gnome xinit /etc/X11/xinit/xinitrc ${0##*/}\n\n(launch Unity from an xterm with session as an argument)\n${0##*/} ubuntu\"\n\nexport DESKTOP_SESSION=\"${DESKTOP_SESSION:-\"$1\"}\"\nexport XDG_SESSION_TYPE=\"${XDG_SESSION_TYPE:-\"x11\"}\"\nexport XDG_SESSION_CLASS=\"user\"\n\nSESSION='gnome-session'\nCINNAMON_SESSION='cinnamon-session'\n\nif [ -z \"$DESKTOP_SESSION\" ]; then\n    echo \"$USAGE\" 1>&2\n    exit 2\nfi\n\n# Cinnamon 2.0 and later uses its own fork of gnome-session\nif [ \"${DESKTOP_SESSION#cinnamon}\" != \"$DESKTOP_SESSION\" ] && \\\n        hash \"$CINNAMON_SESSION\" 2>/dev/null; then\n    SESSION=\"$CINNAMON_SESSION\"\nfi\n\nSESSION_FILE=\"/usr/share/$SESSION/sessions/$DESKTOP_SESSION.session\"\nXDG_CURRENT_DESKTOP=\"$(awk -F \"=\" '/DesktopName/ {print $2}' \"$SESSION_FILE\")\"\nif [ -n \"$XDG_CURRENT_DESKTOP\" ]; then\n    XDG_SESSION_DESKTOP=\"$XDG_CURRENT_DESKTOP\"\n    export XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP\nfi\n\nif [ -z \"$DISPLAY\" ]; then\n    exec xinit /etc/X11/xinit/xinitrc \"$0\"\nelse\n    exec \"$SESSION\" --session=\"$DESKTOP_SESSION\"\nfi\n"
  },
  {
    "path": "chroot-bin/host-dbus",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Either runs the specified command with the environment set to use the host's\n# system dbus instance, or prints out the environment changes required.\n\nexport DBUS_SYSTEM_BUS_ADDRESS='unix:path=/var/host/dbus/system_bus_socket'\nif [ \"$#\" = 0 ]; then\n    echo \"export DBUS_SYSTEM_BUS_ADDRESS='$DBUS_SYSTEM_BUS_ADDRESS'\"\nelse\n    exec \"$@\"\nfi\n"
  },
  {
    "path": "chroot-bin/host-wayland",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Either runs the specified command with the environment set to use the host's\n# wayland server, or prints out the environment changes required.\n\n# If wayland-0 socket does not exit, no Chromium OS wayland server exist\nif [ ! -S \"/var/run/chrome/wayland-0\" ]; then\n    err=\"No Chromium OS Wayland server is available.\"\n    if [ \"$#\" = 0 ]; then\n        echo \"echo '$err' 1>&2\"\n    else\n        echo \"$err\" 1>&2\n        exit 1\n    fi\nelse\n    export XDG_RUNTIME_DIR='/var/run/chrome'\n    if [ \"$#\" = 0 ]; then\n        echo \"export XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR'\"\n    else\n        exec \"$@\"\n    fi\nfi\n"
  },
  {
    "path": "chroot-bin/setres",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Changes the resolution of the current display.\n# If XMETHOD is xiwi, tries to create a new exact resolution, and change mode\n# to that. If that fails (e.g. non-patched xorg-dummy), take the closest,\n# smaller, available resolution in xrandr, and if no smaller resolution is\n# available, pick the closest one.\n# If XMETHOD is anything else, set a resolution from cvt output.\n# In all cases, outputs the applied resolution.\n\nset -e\n\nif [ \"$#\" -lt 2 -o \"$#\" -gt 4 ]; then\n    echo \"USAGE: ${0##*/} x y [r [output]]\" 1>&2\n    exit 2\nfi\nx=\"$1\"\ny=\"$2\"\nr=\"${3:-60}\"\no=\"${4}\"\nif [ -z \"$o\" ]; then\n    o=\"`xrandr -q | awk 'x{print $1;exit}/^Screen 0/{x=1}'`\"\nfi\n\nxmethod=\"`xprop -root 'CROUTON_XMETHOD' | sed -n 's/^.*\\\"\\(.*\\)\\\"/\\1/p'`\"\n\nif [ \"${xmethod%%-*}\" != \"xiwi\" ]; then\n    cvt \"$x\" \"$y\" \"$r\" | {\n        read -r _\n        read -r _ mode data\n        mode=\"${mode#\\\"}\"\n        mode=\"${mode%\\\"}\"\n        xrandr --newmode \"$mode\" $data 2>/dev/null || true\n        xrandr --addmode \"$o\" \"$mode\"\n        xrandr --output \"$o\" --mode \"$mode\"\n        echo \"$mode\"\n    }\n    exit 0\nfi\n\n# Replace mode $2 in output $1, with new data $3..$#\n# Deletes the mode if $3 is not provided\nreplacemode() {\n    local o=\"$1\"\n    local mode=\"$2\"\n    shift 2\n    xrandr --delmode \"$o\" \"$mode\" 2>/dev/null || true\n    xrandr --rmmode \"$mode\" 2>/dev/null || true\n    if [ \"$#\" -gt 0 ]; then\n        xrandr --newmode \"$mode\" \"$@\"\n        xrandr --addmode \"$o\" \"$mode\"\n    fi\n}\n\n# Try to change to arbitrary resolution\nmhz=\"$((r*x*y/1000000))\"\nname=\"kiwi_${x}x${y}_${r}\"\n\n# Try to switch mode, if it already exists.\nif xrandr --output \"$o\" --mode \"$name\" 2>/dev/null; then\n    echo \"${x}x${y}_${r}\"\n    exit 0\nfi\n\n# Add the new mode\nxrandr --newmode \"$name\" $mhz $x $x $x $x $y $y $y $y\nxrandr --addmode \"$o\" \"$name\"\n\n# The next line fails on non-patched xorg-dummy\nif xrandr --output \"$o\" --mode \"$name\"; then\n    # Success: remove old modes\n    others=\"`xrandr | sed -n 's/^.*\\(kiwi[0-9x_]*\\)[^*]*$/\\1/p'`\"\n    for othername in $others; do\n        xrandr --delmode \"$o\" \"$othername\" 2>/dev/null || true\n        xrandr --rmmode \"$othername\" 2>/dev/null || true\n    done\n    echo \"${x}x${y}_${r}\"\n    exit 0\nelse\n    # Delete the new mode\n    xrandr --delmode \"$o\" \"$name\" 2>/dev/null || true\n    xrandr --rmmode \"$name\" 2>/dev/null || true\nfi\n\n# Probably xorg-dummy got overwritten. Recommend an update.\necho \"Failed to set custom resolution. Update your chroot and try again.\" 1>&2\nexit 1\n"
  },
  {
    "path": "chroot-bin/startgnome",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Launches GNOME; automatically falls back to gnome-panel\n\nexec crouton-noroot gnome-session-wrapper gnome\n"
  },
  {
    "path": "chroot-bin/startunity",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Launches Unity; will fall back to Unity-2D on supported releases\n\n# Ensure global app menus work.\nexport UBUNTU_MENUPROXY=1\nexport GTK_MODULES=\"unity-gtk-module\"\n\nexec crouton-noroot gnome-session-wrapper ubuntu\n"
  },
  {
    "path": "chroot-bin/volume",
    "content": "#!/bin/sh\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This script uses amixer to present an interface similar to the brightness\n# script for crouton.\n\nset -e\nset -u\n\nDEFAULT_VOLUME_DELTA=5\n\nAPPLICATION=\"${0##*/}\"\nUSAGE=\"$APPLICATION [set] [0-100]\n$APPLICATION up|down [0-100]\n$APPLICATION mute [set|unset|toggle]\n$APPLICATION get [all|volume|mute]\n\nThis script changes the volume of the current output\ndevice, as would changing it with the shortcut keys or\nwith the GUI in Chrome OS.\n\nShortcut invocations:\n    $APPLICATION up     increases by $DEFAULT_VOLUME_DELTA\n    $APPLICATION down   decreases by $DEFAULT_VOLUME_DELTA\n    $APPLICATION mute   sets muted\n    $APPLICATION 0-100  sets volume to given value\n    $APPLICATION get    shortcut for get all\n\"\n# The control which controls the current output.\nALSA_CONTROL=\"Master\"\n\n# Tiny sugar coating around amixer for common parameters.\n_amixer() {\n    amixer -Dcras \"$@\"\n}\namixer_get_value() {\n    # $1 is the control name to get the value for\n    _amixer cget \"name=$1\" | sed -n \\\n        -e \"/[[:space:]]\\+:/s/.*=//p\"\n    #       ^              ^      ^ Print the line.\n    #       ^              ^ Replace everything before the equal sign.\n    #       ^ Search for the line with spaces and a colon (the value).\n}\n\nget_volume() {\n    amixer_get_value \"$ALSA_CONTROL Playback Volume\"\n}\n\nset_volume() {\n    local volume=\"$1\"\n    _amixer -q sset $ALSA_CONTROL \"$volume\"\n}\n\nget_is_muted() {\n    # Get the alsa state of the control.\n    local state=\"$(amixer_get_value \"$ALSA_CONTROL Playback Switch\")\"\n\n    # Muted is off (control is off)\n    if [ \"$state\" = \"on\" ]; then\n        # Is not muted, so is_muted is false\n        return 1\n    else\n        # Is muted, so is_muted is true\n        return 0\n    fi\n}\n\nget_is_muted_as_text() {\n    if get_is_muted; then\n        echo \"yes\"\n    else\n        echo \"no\"\n    fi\n}\n\ntoggle_mute() {\n    _amixer -q sset $ALSA_CONTROL toggle\n}\n\nmute() {\n    _amixer -q sset $ALSA_CONTROL mute\n}\nunmute() {\n    _amixer -q sset $ALSA_CONTROL unmute\n}\n\nrelative_volume() {\n    local delta=\"$1\"\n\n    # Check that this is integer-ish enough.\n    if ! [ \"$delta\" -eq \"$delta\" ] 2>/dev/null; then\n        error_help \"Error: $APPLICATION <up|down> needs a number [0-100]\"\n        exit 1\n    fi\n\n    if get_is_muted; then\n        if [ \"$delta\" -lt 0 ]; then\n            set_volume 0\n        fi\n        unmute\n    else\n        # Is the volume going down or up?\n        if [ \"$delta\" -lt 0 ]; then\n            # Going down, we strip the leading minus sign,\n            # And suffix the minus sign.\n            set_volume \"${delta#*-}-\"\n        else\n            # Only suffix the plus sign.\n            set_volume \"${delta}+\"\n        fi\n    fi\n}\n\nprint_help() {\n    echo \"$USAGE\"\n}\nerror_help() {\n    # Prints an error message and the usage to stderr\n    [ $# -gt 0 ] && echo \"$@\" 1>&2\n    print_help 1>&2\n}\n\n# Do a sanity check with amixer.\n# An out of date crouton chroot might exhibit problems like:\n#     amixer: Control cras element read error: Input/output error\n# We cannot do it in _amixer since it's executed in a subshell and its value is\n# used as a substitution.\nif ! _amixer > /dev/null 2>&1; then\n    echo \"Failed to communicate with the audio server. Please update your chroot.\" 1>&2\n    exit 2\nfi\n\nif [ $# -lt 1 ]; then\n    error_help \"Error: $APPLICATION needs at least a command.\"\n    exit 1\nfi\n\ncmd=\"$1\"\nshift\n\ncase \"$cmd\" in\nh*|-h*|--help)\n    print_help\n    ;;\nup)\n    relative_volume \"${1:-$DEFAULT_VOLUME_DELTA}\"\n    ;;\ndown)\n    relative_volume \"-${1:-$DEFAULT_VOLUME_DELTA}\"\n    ;;\nmute)\n    action=\"${1-set}\"\n    case \"$action\" in\n    toggle) toggle_mute ;;\n    set) mute ;;\n    unset) unmute ;;\n    *)\n        error_help \"Invalid action: $action for mute.\"\n        exit 1\n        ;;\n    esac\n    ;;\nset|[0-9]|[0-9][0-9]|100)\n    amount=\"$cmd\"\n    # First, check that we received a value to set the volume to.\n    if [ $# -eq 0 ] && [ \"$cmd\" = \"set\" ]; then\n        error_help \"Error: $APPLICATION set needs an amount to set.\"\n        exit 1\n    fi\n    # The value /could/ have been a parameter or the command itself.\n    if [ $# -gt 0 ]; then\n        amount=\"$1\"\n        shift\n    fi\n    # Check that this is integer-ish enough.\n    if ! [ \"$amount\" -eq \"$amount\" ] 2>/dev/null; then\n        error_help \"Error: $APPLICATION set needs a number [0-100]\"\n        exit 1\n    fi\n    set_volume \"$amount\"\n    ;;\nget)\n    action=\"${1-all}\"\n    case \"$action\" in\n    mute) get_is_muted_as_text ;;\n    volume) get_volume ;;\n    all)\n        echo \"Volume: $(get_volume)\"\n        echo \"Muted: $(get_is_muted_as_text)\"\n        ;;\n    *)\n        error_help \"Invalid action: $action for get.\"\n        exit 1\n        ;;\n    esac\n    ;;\n*)\n    error_help \"Error: Unkown command $cmd\"\n    exit 1\n    ;;\nesac\n"
  },
  {
    "path": "chroot-bin/xinit",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Adds a :# to the xinit command line, where # is the first available display\n# number. Also adds the -- to the command line and references the global\n# xserverrc if it isn't already there.  By putting this in /usr/local/bin, PATH\n# will prefer it and scripts that call xinit will automagically work.\n\nxserverrc='/etc/X11/xinit/xserverrc'\ndash='--'\nfor arg in \"$@\"; do\n    if [ -z \"$dash\" ]; then\n        # Check if there's a xserverrc specified.\n        if [ \"${arg#/}\" != \"$arg\" ]; then\n            xserverrc=''\n        fi\n        break\n    elif [ \"$arg\" = '--' ]; then\n        dash=\n    fi\ndone\n\n# Never use display :0 (confusing if aura does not use X11)\ndisp=1\nwhile [ -f \"/tmp/.X$disp-lock\" ]; do\n    disp=$((disp+1))\ndone\n\n# If possible, switch to VT1 to avoid strangeness when launching from VT2\nchvt 1 2>/dev/null || true\n\nexec /usr/bin/xinit /usr/local/bin/croutonxinitrc-wrapper \"$@\" $dash $xserverrc \":$disp\"\n"
  },
  {
    "path": "chroot-bin/xiwi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Runs the specified X11 application in its own X server in Chromium OS.\n\nUSAGE=\"Usage: ${0##*/} [-f] [-F|-T] APPLICATION [PARAMETERS ...]\nLaunches a windowed session in Chromium OS for any graphical application.\nApplications launched in this way show in independent windows or tabs.\nAll parameters are passed to the specified application.\n\nBy default, the app is launched in a window.\n\nOptions:\n    -F          Launch the APPLICATION full-screen.\n    -T          Launch the APPLICATION in a tab.\n    -f          Prevent ${0##*/} from quitting automatically. (see NOTE below)\n\nNOTE:\n${0##*/} will normally close when the application returns. Some gui applications\nfork before or during normal operation, which can confuse ${0##*/} and cause it to\nquit prematurely. If your application does not have a parameter that prevents\nit from forking, and crouton is unable to automatically detect the fork, you can\nuse -f to prevent ${0##*/} from quitting automatically.\n${0##*/} will quit if you close the Chromium OS window when nothing is displayed.\n\nA default window manager will full-screen all windows, unless APPLICATION begins\nwith 'start' or is 'xinit'. You can cycle through multiple windows inside the\napplication via Ctrl-Alt-Tab/Ctrl-Alt-Shift-Tab, or close them via\nCtrl-Alt-Shift-Escape.  If APPLICATION begins with 'start' but you still want to\nuse the default window manager, specify the full path of the application.\"\n\n. \"`dirname \"$0\"`/../installer/functions\"\nxiwicmd=\"`readlink -f -- \"$0\"`\"\nOPTSTRING='FfTt'\n\nif [ \"$#\" = 0 ]; then\n    error 2 \"$USAGE\"\nelif [ \"$1\" = '/' ]; then\n    shift 1\n    foreground=''\n    while getopts \"$OPTSTRING\" f; do\n        case \"$f\" in\n        f) foreground='y';;\n        t|T|F) :;;\n        \\?) error 2 \"$USAGE\";;\n        esac\n    done\n    shift \"$((OPTIND-1))\"\n    xsetroot -cursor_name left_ptr\n    if [ \"$1\" != 'xinit' -a \"${1#start}\" = \"$1\" ]; then\n        i3 -c \"/etc/crouton/xiwi.conf\" &\n        # Wait for i3 to launch\n        xprop -spy -root | grep -q _NET_ACTIVE_WINDOW\n        # Launch the window title monitoring daemon\n        # _NET_ACTIVE_WINDOW is more reliable than _NET_CLIENT_LIST_STACKING for\n        # keeping track of the topmost window.\n        xprop -spy -notype -root 0i ' $0\\n' '_NET_ACTIVE_WINDOW' 2>/dev/null | {\n            name=\"`cat /etc/crouton/name`\"\n            monpid=''\n            monwid=''\n            while read _ wid; do\n                if [ \"$wid\" = \"$monwid\" ]; then\n                    continue\n                fi\n                if [ -n \"$monpid\" ]; then\n                    kill \"$monpid\" 2>/dev/null\n                fi\n                monwid=\"$wid\"\n                (xprop -spy -notype -id \"$wid\" 'WM_NAME' 2>/dev/null || echo) \\\n                    | while read _ title; do\n                        title=\"${title%\\\"}\"\n                        xprop -root -f CROUTON_NAME 8s -set CROUTON_NAME \\\n                            \"$name/$1${title:+\": \"}${title#*\\\"}\"\n                        {\n                            echo -n 'C'\n                            croutoncycle l\n                        } | websocketcommand >/dev/null\n                    done &\n                monpid=\"$!\"\n            done\n            if [ -n \"$monpid\" ]; then\n                kill \"$monpid\" 2>/dev/null\n            fi\n        } &\n        # Launch user init scripts\n        if [ -f \"$HOME/.xiwirc\" ]; then\n            /bin/sh \"$HOME/.xiwirc\" || true\n        fi\n    fi\n    starttime=\"$(date +%s)\"\n    \"$@\"\n    endtime=\"$(date +%s)\"\n    if [ -n \"$foreground\" -o \"$(($endtime-$starttime))\" -le 2 ]; then\n        xprop -spy -notype -root 0i ' $0\\n' 'CROUTON_CONNECTED' \\\n            | while read _ connected; do\n                if [ \"$connected\" != 0 ]; then\n                    continue\n                fi\n                # _NET_CLIENT_LIST_STACKING is more reliable than\n                # _NET_ACTIVE_WINDOW for detecting when no windows exist\n                if ! xprop -notype -root '_NET_CLIENT_LIST_STACKING' \\\n                            | grep -q '0x'; then\n                    kill \"$$\"\n                    break\n                fi\n            done\n    fi\nelse\n    export XMETHOD='xiwi-window'\n    while getopts \"$OPTSTRING\" f; do\n        case \"$f\" in\n        f) :;;\n        F) export XMETHOD='xiwi-fullscreen';;\n        t|T) export XMETHOD='xiwi-tab';;\n        \\?) error 2 \"$USAGE\";;\n        esac\n    done\n    eval \"exe=\\\"\\$$OPTIND\\\"\"\n    if ! hash \"$exe\" 2>/dev/null; then\n        error 2 \"${0##*/}: $exe: not found\"\n    fi\n    exec /usr/local/bin/xinit \"$xiwicmd\" / \"$@\"\nfi\n"
  },
  {
    "path": "chroot-etc/kodi-cycle.py",
    "content": "#!/usr/bin/env python\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Python script to call croutoncycle. This is needed to let the \n# hotkeys ctr-shift-alt F1/F2 work when kodi is in fullscreen.\nimport subprocess\nimport sys\n\nif len(sys.argv) == 2 and sys.argv[1] in (\"prev\", \"next\"):\n  exitcode = subprocess.call([\"/usr/local/bin/croutoncycle\", sys.argv[1]])\nelse:\n  sys.stderr.write(\"Usage: %s prev|next\\n\" % str(sys.argv[0]))\n  exitcode = 2\nsys.exit(exitcode)\n"
  },
  {
    "path": "chroot-etc/kodi-keyboard.xml",
    "content": "<!-- Copyright (c) 2016 The crouton Authors. All rights reserved.           -->\n<!-- Use of this source code is governed by a BSD-style license that can be -->\n<!-- found in the LICENSE file.                                             -->\n<!--                                                                        -->\n<!-- Keymappings kodi for ctr-shift-alt F1/F2 to switch between chroots     -->\n<keymap>\n  <global>\n    <keyboard>\n     <f1 mod=\"ctrl,alt,shift\">RunScript(/etc/crouton/kodi-cycle.py,prev)</f1>\n     <f2 mod=\"ctrl,alt,shift\">RunScript(/etc/crouton/kodi-cycle.py,next)</f2>\n   </keyboard>\n  </global>\n</keymap>\n"
  },
  {
    "path": "chroot-etc/pulseaudio-default.pa",
    "content": "#!/usr/bin/pulseaudio -nF\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Include default configuration first\n.include /etc/pulse/default.pa\n\n# Forward audio to Chromium OS audio server\nload-module module-alsa-sink device=cras sink_name=cras-sink\nload-module module-alsa-source device=cras source_name=cras-source\nset-default-sink cras-sink\nset-default-source cras-source\n"
  },
  {
    "path": "chroot-etc/unity-autostart.desktop",
    "content": "# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n[Desktop Entry]\nType=Application\nName=crouton autostart script for Unity\nExec=/usr/local/bin/crouton-unity-autostart\nOnlyShowIn=Unity;\nNoDisplay=true\nX-GNOME-Autostart-Phase=Initialization\nX-GNOME-Autostart-Notify=true\nX-GNOME-AutoRestart=true\n"
  },
  {
    "path": "chroot-etc/unity-profiled",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# Helper script for Unity called at user login.\n# Right now this just sets up .desktop files in the user's autostart\n# directory to override global autostart.\n\nRELEASE=\"`/usr/local/bin/croutonversion -r`\"\n\n# unity-settings-daemon should run instead of gnome-settings-daemon in trusty\nif [ \"$RELEASE\" = 'trusty' -o \"$RELEASE\" = 'xenial' ]; then\n    autostartdir=\"$HOME/.config/autostart\"\n    mkdir -p \"$autostartdir\"\n    cat > \"$autostartdir\"/gnome-settings-daemon.desktop <<EOF\n[Desktop Entry]\nOnlyShowIn=GNOME;\nEOF\n\nfi\n"
  },
  {
    "path": "chroot-etc/xbindkeysrc.scm",
    "content": ";; Copyright (c) 2016 The crouton Authors. All rights reserved.\n;; Use of this source code is governed by a BSD-style license that can be\n;; found in the LICENSE file.\n\n;; Run xbindkeys -dg for some example configuration file with explanation\n\n; Cycle chroots. On most systems, this is handled by croutontriggerd.\n; On freon, we have to do it ourselves since we currently grab the event device.\n(if (access? \"/sys/class/tty/tty0/active\" F_OK) (begin\n    (xbindkey '(control shift alt F1) \"\")\n    (xbindkey '(control shift alt F2) \"\")\n) (begin\n    (xbindkey '(control shift alt F1) \"xte 'keyup F1'; croutoncycle prev\")\n    (xbindkey '(control shift alt F2) \"xte 'keyup F2'; croutoncycle next\")\n))\n\n; Extra bindings that must only be activated in chroot X11\n(if (not (string-null? (getenv \"XMETHOD\")))\n    (begin\n        ; Brightness control\n        (xbindkey '(XF86MonBrightnessDown) \"brightness down\")\n        (xbindkey '(XF86MonBrightnessUp) \"brightness up\")\n\n        ; Load ~/.xbindkeysrc.scm for customization if the current user has one\n        (let ((usercfg (string-append (getenv \"HOME\") \"/.xbindkeysrc.scm\")))\n            (if (access? usercfg F_OK) (load usercfg))\n        )\n    )\n)\n"
  },
  {
    "path": "chroot-etc/xiwi.conf",
    "content": "# i3 config file (v4)\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Style\nnew_window none\nnew_float normal\nworkspace_layout tabbed\nfont pango:Sans 8\n\n# Colors                border  backgr. text    indicator\nclient.focused          #8E8E8F #EAEAEB #000000 #8E8E8F\nclient.focused_inactive #8E8E8F #CACACB #525252 #8E8E8F\nclient.unfocused        #8E8E8F #CACACB #525252 #8E8E8F\nclient.urgent           #FF8E8E #FFCACB #520000 #FF8E8E\nclient.background       #C3C3C4\n\n# Interaction\nfocus_follows_mouse no\nbindsym Mod1+Shift+Control+Escape kill\nfloating_modifier Mod1\nbindsym Mod1+Tab focus right\nbindsym Mod1+Shift+Tab focus left\nbindsym Mod1+Control+Tab focus right\nbindsym Mod1+Shift+Control+Tab focus left\nbindsym --release button2 kill\n"
  },
  {
    "path": "chroot-etc/xorg-dummy.conf",
    "content": "# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nSection \"Monitor\"\n    Identifier \"Monitor0\"\n    HorizSync 5.0-1000.0\n    VertRefresh 5.0-200.0\nEndSection\n\nSection \"Device\"\n    Identifier \"Card0\"\n    Driver \"dummy\"\n    # Enough memory for 4096x2048\n    VideoRam 32768\nEndSection\n\nSection \"Screen\"\n    DefaultDepth 24\n    Identifier \"Screen0\"\n    Device \"Card0\"\n    Monitor \"Monitor0\"\n    SubSection \"Display\"\n        Depth 24\n        Modes \"1024x768\"\n    EndSubSection\nEndSection\n\nSection \"ServerLayout\"\n    Identifier \"Layout0\"\n    Screen \"Screen0\"\nEndSection\n\nSection \"ServerFlags\"\n    Option \"AutoAddDevices\" \"false\"\n    Option \"AutoAddGPU\" \"false\"\n    Option \"DontVTSwitch\" \"true\"\n    Option \"AllowMouseOpenFail\" \"true\"\n    Option \"PciForceNone\" \"true\"\n    Option \"AutoEnableDevices\" \"false\"\nEndSection\n"
  },
  {
    "path": "chroot-etc/xorg-intel-sna.conf",
    "content": "# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# On Intel platforms with FBC enabled, in order to see anything we need to use\n# the SNA driver with the TearFree option.\nSection \"Device\"\n   Identifier \"Intel Graphics SNA+TearFree\"\n   Driver     \"intel\"\n   Option     \"AccelMethod\" \"sna\"\n   Option     \"TearFree\"    \"true\"\nEndSection\n"
  },
  {
    "path": "chroot-etc/xserverrc",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$XMETHOD\" ]; then\n    if [ -f '/etc/crouton/xmethod' ]; then\n        read -r XMETHOD _ < /etc/crouton/xmethod\n    else\n        echo 'X11 backend not set.' 1>&2\n        exit 1\n    fi\nfi\n\nxserverrc=\"/etc/crouton/xserverrc-${XMETHOD%%-*}\"\nif [ \"${XMETHOD##*/}\" != \"$XMETHOD\" -o ! -f \"$xserverrc\" ]; then\n    echo \"Invalid X11 backend '$XMETHOD'\" 1>&2\n    exit 2\nfi\n\n. \"$xserverrc\"\n"
  },
  {
    "path": "chroot-etc/xserverrc-local.example",
    "content": "#!/bin/sh\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Sample script to customize X server invocation. To activate copy it to\n# /etc/crouton/xserverrc-local\n#\n# The file is sourced before invoking the X server with the variable\n# XMETHOD set to xiwi, or xorg and the variable XARGS containing the\n# command line arguments that will be passed to the server.\n#\n# Uncoment if fonts look too big on machines with 1366x768 11.6\" screen\n#XARGS=\"$XARGS -dpi 135x135\"\n"
  },
  {
    "path": "chroot-etc/xserverrc-xiwi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nlogfile=\"/tmp/Xorg.crouton.$$.log\"\nfor arg in \"$@\"; do\n    disp=\"`echo \"$arg\" | sed -n 's/^\\:\\([0-9]*\\)$/\\1/p'`\"\n    if [ -n \"$disp\" ]; then\n        logfile=\"/tmp/Xorg.crouton.$disp.log\"\n    fi\ndone\n\nif [ \"${XMETHOD%%-*}\" != 'xiwi' ]; then\n    export XMETHOD='xiwi'\nfi\nXARGS=\"-nolisten tcp -config xorg-dummy.conf -logfile $logfile\"\nif [ -f /etc/crouton/xserverrc-local ]; then\n    . /etc/crouton/xserverrc-local\nfi\n\nexec /usr/bin/Xorg $XARGS \"$@\"\n"
  },
  {
    "path": "chroot-etc/xserverrc-xorg",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ \"${XMETHOD%%-*}\" != 'xorg' ]; then\n    export XMETHOD='xorg'\nfi\nXARGS='-nolisten tcp'\nif [ -f /etc/crouton/xserverrc-local ]; then\n    . /etc/crouton/xserverrc-local\nfi\n\nX=/usr/bin/X\n\n# Handle Freon systems\nif [ ! -f \"/sys/class/tty/tty0/active\" ]; then\n    # We won't be able to launch properly if running from frecon\n    ppid=\"$$\"\n    while [ -n \"$ppid\" -a \"$ppid\" -ne 1 ]; do\n        ppid=\"`ps -p \"$ppid\" -o 'ppid=' 2>/dev/null | sed 's/ //g'`\"\n        if ps -p \"$ppid\" -o 'comm=' | grep -q '^frecon$'; then\n            echo 'Xorg X11 servers cannot be launched from Frecon.' 1>&2\n            echo 'Return to Chromium OS and use crosh to launch X.' 1>&2\n            exit 2\n        fi\n    done\n    # Prepare lock file\n    mkdir -p '/tmp/crouton-lock'\n    touch '/tmp/crouton-lock/display'\n    chmod -Rf g+rwX '/tmp/crouton-lock'\n    chgrp -Rf crouton '/tmp/crouton-lock'\n    # Freon necessitates the preload hack for X to coexist\n    X=/usr/bin/Xorg\n    logfile=\"/tmp/Xorg.crouton.$$.log\"\n    for arg in \"$@\"; do\n        disp=\"`echo \"$arg\" | sed -n 's/^\\:\\([0-9]*\\)$/\\1/p'`\"\n        if [ -n \"$disp\" ]; then\n            logfile=\"/tmp/Xorg.crouton.$disp.log\"\n        fi\n    done\n    XARGS=\"$XARGS -logfile $logfile\"\n    export LD_PRELOAD=\"/usr/local/lib/croutonfreon.so:$LD_PRELOAD\"\nfi\n\nexec \"$X\" $XARGS \"$@\"\n"
  },
  {
    "path": "host-bin/crash_reporter_wrapper",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# When launched from the kernel, PATH is not set and stderr is closed\nif [ ! -t 0 ]; then\n    exec 2>/var/log/crash_reporter_wrapper.log\n    export PATH='/bin:/sbin:/usr/bin:/usr/sbin'\nfi\n\nAPPLICATION=\"${0##*/}\"\nCORE_PATTERN_PARAMS='%p %s %u %g %e %h %t %E'\nCORE_PATTERN_PARAMS_COUNT=8\nCORE_PATTERN=\"|`readlink -f -- \"$0\"` $CORE_PATTERN_PARAMS\"\nCORE_PATTERN_FILE='/var/run/crw/core_pattern'\nCORE_PATTERN_PROC='/proc/sys/kernel/core_pattern'\nDEFAULT_CHROOT_PATTERN=''\nIDEAL_LOCATION='/var/run/crw/crw'\n\nUSAGE=\"$APPLICATION register\n$APPLICATION $CORE_PATTERN_PARAMS\n\nHandles system core dumps, passing the dump through to Chromium OS's\ncrash_reporter or processing it as the chroot requests, depending on what\nnamespace the process belongs to.\n\nThe first form should be run as root, and sets $CORE_PATTERN_PROC\nto use this script as the core dump pipe program.\n\nThe second form is expected to be called by the kernel upon program crash, via\nthe core_pattern set with the first form. The script checks if the crashing\nPID's root has an /etc/crouton/core_pattern and uses that to emulate the\nkernel's handling of the core_pattern proc entry.\"\n\n\n# Outputs a parameter by core_pattern name (e.g. %p prints out the expanded $1)\n# If the parameter doesn't exist, outputs an error to stderr and returns $1\n# $1: the parameter name, either in the form of \"%p\" or just \"p\"\n# $2+: must be \"$@\"\nget_parameter() {\n    local param=\"%${1#%}\" param_names=\"$CORE_PATTERN_PARAMS \"\n    if [ \"$param\" = '%%' ]; then\n        echo -n '%'\n        return\n    elif [ \"$param\" = '%c' ]; then\n        echo -n \"$core_limit\"\n        return\n    fi\n    shift\n    while [ \"$param\" != \"${param_names%% *}\" ]; do\n        param_names=\"${param_names#* }\"\n        if [ -z \"$param_names\" ]; then\n            echo \"$param not provided for core_pattern\" 1>&2\n            echo -n \"$param\"\n            return\n        fi\n        shift\n    done\n    echo -n \"$1\"\n}\n\n# Takes the specified string and expands included parameters\n# Outputs result to stdout\n# $1: the string to expand and escape\n# $2+: must be \"$@\"\nexpand_parameters() {\n    local unexpanded=\"$1%\" remain param\n    shift\n    while [ -n \"$unexpanded\" ]; do\n        param=\"${unexpanded%%%*}\"\n        echo -n \"$param\"\n        remain=\"${unexpanded#*%?}\"\n        if [ \"$remain\" = \"$unexpanded\" ]; then\n            break\n        fi\n        param=\"${unexpanded#\"$param\"}\"\n        param=\"${param%\"$remain\"}\"\n        unexpanded=\"$remain\"\n        get_parameter \"$param\" \"$@\"\n    done\n}\n\n# Takes stdin and escapes it to be safe within single-quotes\n# Outputs result to stdout\n# Converts ' to '\\''\nescape() {\n    sed \"s/'/'\\\\\\\\\\\\''/g\"\n}\n\n\nif [ \"$#\" != 1 -a \"$#\" != \"$CORE_PATTERN_PARAMS_COUNT\" ]; then\n    echo \"$USAGE\" 1>&2\n    exit 2\nfi\n\nif [ \"$#\" = 1 -a \"$1\" != 'register' ]; then\n    echo \"$USAGE\" 1>&2\n    exit 2\nfi\n\nif [ \"${UID:-0}\" != 0 -o \"${USER:-root}\" != 'root' ]; then\n    echo \"$APPLICATION must be run as root.\" 1>&2\n    exit 2\nfi\n\nif [ \"$#\" = 1 ]; then\n    # Always register the script from /var/run for safety\n    if [ \"$0\" != \"$IDEAL_LOCATION\" ]; then\n        dir=\"${IDEAL_LOCATION%/*}\"\n        if ! mountpoint \"$dir\" >/dev/null 2>/dev/null; then\n            mkdir -p \"$dir\"\n            mount -t tmpfs \\\n                  -o 'rw,nosuid,nodev,exec,noatime,mode=700,size=128K' \\\n                  tmpfs \"$dir\"\n        fi\n        cp -fT \"$0\" \"$IDEAL_LOCATION\"\n        chmod 500 \"$IDEAL_LOCATION\"\n        exec \"$IDEAL_LOCATION\" \"$@\"\n    fi\n    # Store Chromium OS's core pattern for passthrough usage\n    if [ ! -f \"$CORE_PATTERN_FILE\" ]; then\n        cat \"$CORE_PATTERN_PROC\" > \"$CORE_PATTERN_FILE\"\n    fi\n    # Register ourselves as the coredump handler\n    echo \"$CORE_PATTERN\" > \"$CORE_PATTERN_PROC\"\n    exit 0\nfi\n\n# It's a core dump! See CORE(5) for details in emulating core_pattern.\npattern=''\n\n# $1 is the pid of the process. Check the process's root for etc/crouton\npid=\"$1\"\nroot=\"/proc/$pid/root\"\ncwd=\"/proc/$pid/cwd\"\ncroutondir=\"$root/etc/crouton\"\nif [ -d \"$croutondir\" ]; then\n    # Looks like a chroot (or a very weird rootfs, but that's unlikely)\n    # Grab the chroot's pattern or use the default if the file doesn't exist\n    if [ -f \"$croutondir/core_pattern\" ]; then\n        # File exists; first non-empty, non-comment line is the core_pattern\n        pattern=\"`awk '/^[^#]/ { print $0; exit }' \"$croutondir/core_pattern\"`\"\n    else\n        pattern=\"$DEFAULT_CHROOT_PATTERN\"\n    fi\nelse\n    # Probably a Chromium process. Grab the backed-up core pattern.\n    if [ -f \"$CORE_PATTERN_FILE\" ]; then\n        pattern=\"`cat \"$CORE_PATTERN_FILE\"`\"\n    else\n        echo \"$APPLICATION was not properly registered as the core_pattern.\" 1>&2\n        echo \"You must register using '$0 register'\" 1>&2\n        exit 2\n    fi\nfi\n\n# If the pattern is empty, just exit.\nif [ -z \"$pattern\" ]; then\n    exit 0\nfi\n\n# Remove the pipe character so we can play with the path generically\nispipe=''\nif [ \"${pattern#|}\" != \"$pattern\" ]; then\n    ispipe='y'\n    pattern=\"${pattern#|}\"\n    # Pipe paths must be absolute\n    if [ \"${pattern#/}\" = \"$pattern\" ]; then\n        echo \"core_pattern '|$pattern' is not absolute\" 1>&2\n        exit 2\n    fi\nfi\n\n# Get the core size limit for the process\ncore_limit=\"`awk '/^Max core file size/ {\n                printf ($5==\"unlimited\") ? -1 : $5; exit\n            }' \"/proc/$pid/limits\"`\"\n\n# Prepare the file to operate on\nif [ -z \"$ispipe\" ]; then\n    # Don't bother if core file size limit is 0\n    if [ \"$core_limit\" = 0 ]; then\n        exit 0\n    fi\n    # If this is not a pipe, we need to expand the %'s here.\n    file=\"`expand_parameters \"$pattern\" \"$@\"`\"\nelse\n    # For pipes, path is anything up to a space\n    file=\"${pattern%% *}\"\n    pattern=\"${pattern#\"$file\"}\"\nfi\n\n# Fix up the path to be working directory-relative\nif [ \"${file#/}\" = \"$file\" ]; then\n    file=\"$cwd/$file\"\nfi\n\n# Canonicalize within the chroot and escape the filename to work within quotes\nfile=\"`chroot \"$root\" readlink -m -- \"/${file%/*}\"`/${file##*/}\"\nfile=\"`echo -n \"$file\" | escape`\"\n\n# Prepare the command to run inside the chroot as the appropriate user\nif [ -z \"$ispipe\" ]; then\n    cmd=\"\n        if [ -e '$file' -a ! -f '$file' ] || [ -h '$file' ]; then\n            echo \\\"Not overwriting '$file'; it is not a regular file\\\" 1>&2\n        elif [ -f '$file' ] && [ \\\"\\`stat -c '%h' '$file'\\`\\\" != 1 ]; then\n            echo \\\"Not overwriting '$file'; it is multiply linked\\\" 1>&2\n        elif [ '$core_limit' = -1 ] && cat > '$file'; then\n            exit 0\n        elif [ '$core_limit' != -1 ] && head -c '$core_limit' > '$file'; then\n            exit 0\n        fi\n        exit 1\n    \"\n    username=\"`chroot \"$root\" ps -ouser= -p \"$pid\"`\"\nelse\n    # Finally need to expand the parameters here\n    cmd=\"exec '$file'\"\n    params=\"$pattern \"\n    while [ -n \"$params\" ]; do\n        param=\"${params%% *}\"\n        params=\"${params#* }\"\n        if [ -n \"$param\" ]; then\n            cmd=\"$cmd '`expand_parameters \"$param\" \"$@\" | escape`'\"\n        fi\n    done\n    username='root'\nfi\n\n# Run the generated command within the appropriate chroot\nexec chroot \"$root\" su -s '/bin/sh' -c \"$cmd\" - \"$username\"\n"
  },
  {
    "path": "host-bin/edit-chroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nALLCHROOTS=''\nBACKUP=''\nBINDIR=\"`dirname \"\\`readlink -f -- \"$0\"\\`\"`\"\nCHROOTS=\"`readlink -m -- \"$BINDIR/../chroots\"`\"\nDELETE=''\nENCRYPT=''\nKEYFILE=''\nLISTDETAILS=''\nMOVE=''\nCOPY=''\nNEEDS_UNMOUNT=''\nRESTORE=''\nSPLIT=''\nTARBALL=''\nYES=''\nYESPARAM=''\n\nUSAGE=\"$APPLICATION [options] name [...]\n\nEdits a chroot.\n\nOptions:\n    -a          Operates on all chroots in $CHROOTS.\n                If no other operation is specified, prints out the names of the chroots.\n    -c CHROOTS  Directory the chroots are in. Default: $CHROOTS\n    -b          Backs up the chroot to a tarball. Compression format is chosen\n                based on the tarball extension. Backups always take place before\n                other actions on a given chroot.\n    -d          Deletes the chroot. Assumed if run as delete-chroot.\n    -e          If the chroot is not encrypted, encrypt it.\n                If it is encrypted, change the encryption passphrase.\n    -f TARBALL  When used with -b, overrides the default tarball to back up to.\n                If unspecified, assumes NAME-yyyymmdd-hhmm.tar[.gz], where .gz\n                is included for unencrypted chroots, and not for encrypted ones.\n                When used with -r, specifies the tarball to restore from.\n                If TARBALL is a directory, automatic naming is still used.\n                If multiple chroots are specified, TARBALL must be a directory.\n    -k KEYFILE  File or directory to store the (encrypted) encryption keys in.\n                If unspecified, the keys will be stored in the chroot if doing a\n                first encryption, or left in place on existing chroots.\n                If specified, keyfile will be moved. Specify a dash - as the\n                KEYFILE to move the key back into the chroot.\n                If multiple chroots are specified, KEYFILE must either be -\n                or a directory.\n    -l          Prints out croutonversion details on the chroot, if available.\n                Specify twice to prompt and unlock encrypted chroots as necessary.\n    -m DEST     Moves a chroot. Specify a new name to keep it in the same\n                directory, or an absolute path to move it entirely.\n                DEST can be a directory, in which case it must end in a slash.\n                If multiple chroots are specified, DEST must be a directory.\n                If you are moving a chroot to a SD card/USB drive, make sure the\n                storage is formatted to ext2/3/4.\n    -C DEST     Like -m, but copies instead of moves.\n    -r          Restores a chroot from a tarball. The tarball path can be\n                specified with -f or detected from name. If both are specified,\n                restores to that name instead of the one in the tarball.\n                Will not overwrite a chroot when restoring unless -r is\n                specified twice.\n    -s SPLIT    Force a backup archive to be split into SPLIT-sized chunks.\n                SPLIT is specified in megabytes (1048576 bytes), and cannot be\n                smaller than 10.\n                FAT32 filesystems are split by default to fit within 4GB.\n    -y          Do all actions without confirmation.\"\n\n# Common functions\n. \"$BINDIR/../installer/functions\"\n\n# Process arguments\ngetopts_string='abc:C:def:k:lm:rs:y'\nwhile getopts_nextarg; do\n    case \"$getopts_var\" in\n    a) ALLCHROOTS='y';;\n    b) BACKUP='y'; NEEDS_UNMOUNT='y';;\n    c) CHROOTS=\"`readlink -m -- \"$getopts_arg\"`\";;\n    C) COPY=\"$getopts_arg\"; NEEDS_UNMOUNT='y';;\n    d) DELETE='y'; NEEDS_UNMOUNT='y';;\n    e) ENCRYPT='y'; NEEDS_UNMOUNT='y';;\n    f) TARBALL=\"$getopts_arg\";;\n    k) KEYFILE=\"$getopts_arg\";;\n    l) LISTDETAILS=$(($LISTDETAILS+1));;\n    m) MOVE=\"$getopts_arg\"; NEEDS_UNMOUNT='y';;\n    r) RESTORE=$(($RESTORE+1)); NEEDS_UNMOUNT='y';;\n    s) SPLIT=\"$getopts_arg\";;\n    y) YES='a'; YESPARAM='-y';;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\n\n# If the executable name is delete*, assume DELETE.\nif [ \"${APPLICATION#delete}\" != \"$APPLICATION\" ]; then\n    DELETE='y'\n    NEEDS_UNMOUNT='y'\nfi\n\n# At least one command must be specified\nif [ -z \"$ALLCHROOTS$BACKUP$DELETE$ENCRYPT$KEYFILE$LISTDETAILS$MOVE$COPY$RESTORE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Need at least one chroot listed if not using -r with -f or -a.\nif [ $# = 0 ] && ! [ -n \"$RESTORE\" -a -n \"$TARBALL\" -o -n \"$ALLCHROOTS\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Cannot specify a chroot and -a.\nif [ $# -gt 0 -a -n \"$ALLCHROOTS\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# -f without -r or -b doesn't make sense\nif [ -n \"$TARBALL\" -a -z \"$BACKUP$RESTORE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Cannot specify both backup and restore.\nif [ -n \"$BACKUP\" -a -n \"$RESTORE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Cannot specify both copy and move.\nif [ -n \"$COPY\" -a -n \"$MOVE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Cannot specify delete with anything else.\nif [ -n \"$DELETE\" -a -n \"$BACKUP$ENCRYPT$KEYFILE$MOVE$COPY$RESTORE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Make sure SPLIT is reasonable\nif [ -n \"$SPLIT\" ] && [ \"$SPLIT\" -lt 10 -o -z \"$BACKUP\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# If we specified -a option, bring in all chroots.\nif [ -n \"$ALLCHROOTS\" ]; then\n    if [ ! -d \"$CHROOTS\" ]; then\n        error 1 \"$CHROOTS not found.\"\n    fi\n    for crname in \"$CHROOTS\"/*; do\n        if [ -d \"$crname\" ]; then\n            set -- \"$@\" \"${crname##*/}\"\n        fi\n    done\n    if [ $# = 0 ]; then\n        error 1 \"No chroots found in $CHROOTS\"\n    fi\nfi\n\n# If TARBALL is unspecified and we're in /, put the tarball in ~/Downloads\nif [ -n \"$BACKUP$RESTORE\" -a -z \"$TARBALL\" -a \"$PWD\" = '/' \\\n        -a -d '/home/chronos/user/Downloads' ]; then\n    TARBALL=\"/home/chronos/user/Downloads/\"\nfi\n\n# If multiple chroots are listed, KEYFILE and MOVE and COPY must be empty or directories.\nif [ $# -gt 1 -a -f \"$KEYFILE\" -a \"$KEYFILE\" != '-' ]; then\n    error 2 \"Multiple chroots specified, but $KEYFILE is not a directory.\"\nelif [ $# -gt 1 -a -n \"$MOVE\" -a \"${MOVE%/}\" = \"$MOVE\" ]; then\n    error 2 \"Multiple chroots specified, but $MOVE is not a directory.\"\nelif [ $# -gt 1 -a -n \"$COPY\" -a \"${COPY%/}\" = \"$COPY\" ]; then\n    error 2 \"Multiple chroots specified, but $COPY is not a directory.\"\nelif [ $# -gt 1 -a -f \"$TARBALL\" ]; then\n    error 2 \"Multiple chroots specified, but $TARBALL is not a directory.\"\nfi\n\n# Don't allow moving to non-ext filesystems (but don't check if just renaming)\nif [ -n \"$MOVE\" -a \"${MOVE#*/}\" != \"$MOVE\" ] && \\\n        df -T \"`getmountpoint \"$MOVE\"`\" | awk '$2~\"^ext\"{exit 1}'; then\n    error 2 \"Chroots can only be moved to ext filesystems.\"\nfi\n\n# Don't allow copying to non-ext filesystems (but don't check if in same directory)\nif [ -n \"$COPY\" -a \"${COPY#*/}\" != \"$COPY\" ] && \\\n        df -T \"`getmountpoint \"$COPY\"`\" | awk '$2~\"^ext\"{exit 1}'; then\n    error 2 \"Chroots can only be copied to ext filesystems.\"\nfi\n\n# Don't allow restoring to non-ext filesystems\nif [ -n \"$RESTORE\" ] && \\\n        df -T \"`getmountpoint \"$CHROOTS\"`\" | awk '$2~\"^ext\"{exit 1}'; then\n    error 2 \"Chroots can only be restored to ext filesystems.\"\nfi\n\n# Don't allow backing up to tmpfs filesystems\nif [ -n \"$BACKUP\" ] && ! df -T \"`getmountpoint \"${TARBALL:-.}\"`\" \\\n                            | awk '$2==\"tmpfs\"{exit 1}'; then\n    error 2 \"Chroots cannot be backed up to temporary filesystems.\"\nfi\n\n# We need to run as root\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    error 2 \"$APPLICATION must be run as root.\"\nfi\n\n# Strongly advise against moving a keyfile to a tmpfs path\nif [ \"${KEYFILE:--}\" != '-' ] && \\\n        ! df -T \"`getmountpoint \"$KEYFILE\"`\" | awk '$2==\"tmpfs\"{exit 1}'; then\n    echo -n '\\\nMoving a keyfile to a temporary filesystem is a really good way to permanently\nlose access to your chroot. If you still want to do this, wait for 15 seconds.\nOtherwise: HIT CTRL-C RIGHT NOW > ' 1>&2\n    sleep 15\n    echo \\\n'...okay. Be sure to put the keyfile somewhere safe before you reboot.' 1>&2\nfi\n\n# If we're restoring and specified a tarball and no name, detect the name.\nif [ -n \"$RESTORE\" -a -n \"$TARBALL\" -a $# = 0 ]; then\n    echo 'Detecting chroot name...' 1>&2\n    label=\"`tar --test-label -f \"$TARBALL\" 2>/dev/null`\"\n    if [ -n \"$label\" ]; then\n        if [ \"${label#crouton:backup}\" = \"$label\" ]; then\n            error 2 \"$TARBALL doesn't appear to be a valid crouton backup.\"\n        fi\n        NAME=\"${label#*-}\"\n    else\n        # Old backups just use the first folder name\n        NAME=\"`tar -tf \"$TARBALL\" 2>/dev/null | head -n 1`\"\n        NAME=\"${NAME%%/*}\"\n    fi\n    if [ -z \"$NAME\" ]; then\n        error 2 \"$TARBALL doesn't appear to be a valid tarball.\"\n    fi\n    set -- \"$NAME\"\nfi\n\n# Make sure that all names are valid chroot names.\n# For anything but restore, make sure all chroots exist before doing anything.\nfor NAME in \"$@\"; do\n    if ! validate_name \"$NAME\"; then\n        error 2 \"Invalid chroot name '$NAME'.\"\n    fi\n\n    if [ -z \"$RESTORE\" ]; then\n        CHROOT=\"$CHROOTS/$NAME\"\n\n        # Check for existence\n        if [ ! -d \"$CHROOT\" ]; then\n            extra=''\n            if [ \"${NAME#-}\" != \"$NAME\" ]; then\n                extra=\"\nPlease specify all options before chroot names.\"\n            fi\n            error 2 \"$CHROOT not found.$extra\"\n        fi\n    fi\ndone\n\n# Display all chroot names if using -a without any other operations.\nif [ -n \"$ALLCHROOTS\" -a -z \"$BACKUP$DELETE$ENCRYPT$KEYFILE$LISTDETAILS$MOVE$COPY$RESTORE\" ]; then\n    echo \"$@\"\n    exit 0\nfi\n\n# If TARBALL ends in a slash or we're restoring multiple chroots, make directory\nif [ -n \"$TARBALL\" ] && \\\n        [ $# -ge 2 -o -d \"$TARBALL\" -o \"${TARBALL%/}\" != \"$TARBALL\" ]; then\n    TARBALL=\"${TARBALL%/}/\"\n    mkdir -p \"$TARBALL\"\nfi\n\n# Avoid kernel panics due to slow I/O\ndisablehungtask\n\n# Make sure we always exit with echo on the tty.\naddtrap \"stty echo 2>/dev/null\"\n\n# Prints out a fancy spinner that updates every time a line is fed in, unless\n# the output is not to a tty, in which case it just prints a new line.\n# $1: number of lines between each update of the spinner\n# $2...: Command to be run\n# Erases the line each time, so it will always be at position 0.\n# Either expect this and put text later in the line, or give this its own line.\nspinner() {\n    local spin=\"$1\"\n    shift\n    if [ -t 2 ]; then\n        # Keep track of the exit status of the piped command\n        local ret=\"`((\"$@\" || echo \"$?\" 1>&3) | mawk -Winteractive '\n            BEGIN {\n                printf \"\\r\"\n            }\n            {\n                y = (y+1) % '\"$spin\"'\n                if (y == 0) {\n                    x = (x+1) % 4\n                    printf substr(\"\\|/-\", x+1, 1) \"\\r\"\n                }\n            }' 1>&2) 3>&1`\"\n        if [ -n \"$ret\" ]; then\n            return \"$ret\"\n        fi\n    else\n        echo 1>&2\n        \"$@\" 1>/dev/null\n    fi\n}\n\n# Process each chroot\nfor NAME in \"$@\"; do\n    # Double check $NAME (better paranoid than sorry)\n    if ! validate_name \"$NAME\"; then\n        error 2 \"Invalid chroot name '$NAME'.\"\n    fi\n    CHROOT=\"$CHROOTS/$NAME\"\n\n    # Check for existence and unmount/delete the chroot.\n    if [ -d \"$CHROOT\" ]; then\n        if [ -n \"$LISTDETAILS\" ]; then\n            getversion='y'\n            echo \"name: $NAME\"\n            if [ -f \"$CHROOT/.ecryptfs\" ]; then\n                if [ ! -d \"/var/run/crouton/$CHROOT\" \\\n                        -a \"$LISTDETAILS\" -gt 1 ]; then\n                    sh \"$BINDIR/mount-chroot\" -c \"$CHROOTS\" -- \"$NAME\" || true\n                fi\n                if mountpoint -q \"/var/run/crouton/$CHROOT\"; then\n                    echo \"encrypted: yes, unlocked\"\n                else\n                    echo \"encrypted: yes, locked\"\n                    getversion='n'\n                fi\n            else\n                echo \"encrypted: no\"\n            fi\n            if [ \"$getversion\" = 'y' ]; then\n                CROUTON_NO_UNMOUNT=1 sh \"$BINDIR/enter-chroot\" \\\n                    -c \"$CHROOTS\" -n \"$NAME\" -x /usr/local/bin/croutonversion \\\n                    || true\n            fi\n        fi\n        if [ \"$RESTORE\" = 1 ]; then\n            error 2 \"$CHROOT already exists! Specify a second -r to overwrite it (dangerous).\"\n        elif [ -n \"$RESTORE\" ]; then\n            EXISTS='y'\n        elif ! sh -e \"$BINDIR/unmount-chroot\" $YESPARAM \\\n                    -c \"$CHROOTS\" -- \"$NAME\"; then\n            if [ -n \"$NEEDS_UNMOUNT\" ]; then\n                exit 1\n            fi\n        fi\n    elif [ -n \"$RESTORE\" ]; then\n        EXISTS=''\n    else\n        # This should have been caught earlier\n        error 2 \"$CHROOT not found.\"\n    fi\n\n    # Delete the chroot?\n    if [ -n \"$DELETE\" ]; then\n        # Confirm deletion\n        if [ \"${YES#[Aa]}\" = \"$YES\" ]; then\n            echo -n \"Delete $CHROOT? [a/y/N] \" 1>&2\n            if [ -n \"$CROUTON_EDIT_RESPONSE\" ]; then\n                YES=\"$CROUTON_EDIT_RESPONSE\"\n                echo \"$YES\" 1>&2\n            else\n                read -r YES\n            fi\n            if [ \"${YES#[AaYy]}\" = \"$YES\" ]; then\n                 error 2 \"Aborting deletion of $CHROOT\"\n            fi\n        fi\n        # Delete the chroot\n        echo -n \"  Deleting $CHROOT...\" 1>&2\n        spinner 1000 rm -rvf --one-file-system \"$CHROOT\"\n        echo \"Finished deleting $CHROOT\" 1>&2\n        continue\n    fi\n\n    # Backup the chroot\n    if [ -n \"$BACKUP\" ]; then\n        dest=\"$TARBALL\"\n        date=\"`date '+%Y%m%d-%H%M'`\"\n        if [ -z \"$dest\" -o -d \"$TARBALL\" ]; then\n            dest=\"$TARBALL$NAME-$date.tar\"\n            # Only compress if it's not encrypted (it'd be a waste of time)\n            if [ ! -f \"$CHROOT/.ecryptfs\" ]; then\n                dest=\"$dest.gz\"\n            fi\n        fi\n        # If we're writing to a fat32 filesystem, split the file at 4GB chunks\n        if ! df -T \"`getmountpoint \"$dest\"`\" | awk '$2~\"^v?fat\"{exit 1}'; then\n            SPLIT=\"${SPLIT:-4095}\"\n        fi\n        if [ -n \"$SPLIT\" ]; then\n            tardest=\"`mktemp -d --tmpdir=/tmp 'crouton-backup.XXX'`\"\n            addtrap \"rm -rf '$tardest'\"\n            tardest=\"$tardest/pipe.${dest##*/}\"\n            mkfifo -m 600 \"$tardest\"\n            split -b \"${SPLIT}m\" -a 4 \"$tardest\" \"$dest.part-\" &\n            splitpid=\"$!\"\n        else\n            tardest=\"$dest\"\n            splitpid=''\n        fi\n        echo -n \"  Backing up $CHROOT to $(readlink -m -- \"$dest\")...\" 1>&2\n        addtrap \"echo 'Deleting partial archive.' 1>&2; \\\n                 kill '$splitpid' 2>/dev/null; rm -f '$dest' '$dest.part-'*\"\n        ret=0\n        spinner 1 tar --checkpoint=100 --checkpoint-action=exec=echo \\\n            --one-file-system -V \"crouton:backup.${date%-*}${date#*-}-$NAME\" \\\n            -caf \"$tardest\" -C \"$CHROOTS\" \"$NAME\" || ret=\"$?\"\n        if [ -n \"$SPLIT\" ]; then\n            wait \"$splitpid\" || ret=\"$?\"\n            mv -f \"$dest.part-aaaa\" \"$dest\" || ret=\"$?\"\n        fi\n        if [ \"$ret\" -ne 0 ]; then\n            echo \"Unable to backup $CHROOT.\" 1>&2\n            exit \"$ret\"\n        fi\n        # Make sure filesystem is sync'ed\n        sync\n        undotrap\n        echo \"Finished backing up $CHROOT to $dest\" 1>&2\n    fi\n\n    # Restore the chroot\n    if [ -n \"$RESTORE\" ]; then\n        src=\"$TARBALL\"\n        if [ -z \"$src\" -o -d \"$TARBALL\" ]; then\n            src=''\n            file=\"$TARBALL$NAME\"\n            # Search for the alphabetically last tarball with src.\n            # Dated tarballs take precedence over undated tarballs.\n            for file in \"$file.\"* \"$file-\"*; do\n                if [ \"${file%.part-[a-z][a-z][a-z][a-z]}\" != \"$file\" \\\n                        -o ! -f \"$file\" ]; then\n                    continue\n                fi\n                # Confirm it's a tarball\n                if ! tar --test-label -f \"$file\" >/dev/null 2>&1; then\n                    continue\n                fi\n                # Since * alphabetizes, always keep the last one\n                src=\"$file\"\n            done\n            if [ -z \"$src\" ]; then\n                error 2 \"Unable to find a tarball for $NAME. You can specify one with -f.\"\n            fi\n            echo \"Found $src for restoring $NAME.\" 1>&2\n        elif ! tar --test-label -f \"$src\" >/dev/null 2>&1; then\n            error 2 \"$src doesn't appear to be a valid tarball.\"\n        fi\n        if [ -n \"$EXISTS\" ]; then\n            echo \"WARNING: $CHROOT already exists. Deleting it before restoring.\" 1>&2\n            echo \"Press Control-C to abort; restoration will continue in 5 seconds.\" 1>&2\n            sleep 5\n            sh -e \"$BINDIR/edit-chroot\" -d -y -c \"$CHROOTS\" \"$NAME\"\n        fi\n        echo -n \"  Restoring $src to $CHROOT...\" 1>&2\n        mkdir -p \"$CHROOT\"\n        if [ -f \"$src.part-aaab\" ]; then\n            # Detect the type of compression before sending it through a fifo\n            for tarparam in -z -j -J -Z --no-auto-compress fail; do\n                if [ \"$tarparam\" = 'fail' ]; then\n                    error 2 \"Unable to detect compression method of $src\"\n                elif tar $tarparam --test-label -f \"$src\" >/dev/null 2>&1; then\n                    break\n                fi\n            done\n            # Don't let tar get tripped up by cat's incomplete writes\n            tarparam=\"$tarparam -B\"\n            tarsrc=\"`mktemp -d --tmpdir=/tmp 'crouton-restore.XXX'`\"\n            addtrap \"rm -rf '$tarsrc'\"\n            tarsrc=\"$tarsrc/pipe\"\n            mkfifo -m 600 \"$tarsrc\"\n            cat \"$src\" \"$src.part\"* >> \"$tarsrc\" &\n            catpid=\"$!\"\n            addtrap \"kill '$catpid' 2>/dev/null\"\n        else\n            tarsrc=\"$src\"\n            tarparam='-a'\n            catpid=''\n        fi\n        spinner 1 tar --checkpoint=200 --checkpoint-action=exec=echo $tarparam \\\n            --one-file-system -xf \"$tarsrc\" -C \"$CHROOT\" --strip-components=1\n        if [ -n \"$catpid\" ]; then\n            wait \"$catpid\"\n        fi\n        # Make sure filesystem is sync'ed\n        sync\n        echo \"Finished restoring $src to $CHROOT\" 1>&2\n    fi\n\n    # Update the keyfile\n    if [ -n \"$KEYFILE\" ]; then\n        newkeyfile=\"$KEYFILE\"\n        # Find the current keyfile\n        oldkeyfile=\"$CHROOT/.ecryptfs\"\n        if [ -f \"$oldkeyfile\" ]; then\n            header=\"`head -n1 \"$oldkeyfile\"`\"\n            if [ -n \"$header\" ]; then\n                oldkeyfile=\"$header\"\n            fi\n        fi\n        if [ \"$newkeyfile\" = '-' ]; then\n            newkeyfile=\"$CHROOT/.ecryptfs\"\n        fi\n        if [ \"${newkeyfile#/}\" = \"$newkeyfile\" ]; then\n            newkeyfile=\"$PWD/$newkeyfile\"\n        fi\n        if [ -d \"$newkeyfile\" -o \"${newkeyfile%/}\" != \"$newkeyfile\" ]; then\n            newkeyfile=\"${newkeyfile%/}/$NAME\"\n        fi\n        oldkeyfile=\"`readlink -m -- \"$oldkeyfile\"`\"\n        keyfilecanon=\"`readlink -m -- \"$newkeyfile\"`\"\n        if [ ! -f \"$oldkeyfile\" ]; then\n            # If there is no old keyfile, make sure we've requested encryption.\n            if [ -z \"$ENCRYPT\" ]; then\n                error 1 \"Old key file not found\"\n            fi\n        elif [ \"$oldkeyfile\" != \"$keyfilecanon\" ]; then\n            chrootecryptfsfile=\"`readlink -m -- \"$CHROOT/.ecryptfs\"`\"\n            # Don't clobber a file already there\n            if [ -e \"$newkeyfile\" -a \"$newkeyfile\" != \"$chrootecryptfsfile\" ]; then\n                error 1 \"Encryption key file $newkeyfile already exists. Refusing to overwrite!\"\n            fi\n            # Write the new keyfile before removing the old.\n            if ! mkdir -p \"`dirname \"$newkeyfile\"`\"; then\n                error 1 \"Unable to create directory for $newkeyfile\"\n            fi\n            echo \"Moving key file from $oldkeyfile to $newkeyfile\" 1>&2\n            (echo; tail -n+2 \"$oldkeyfile\") > \"$newkeyfile\"\n            # Remove old keyfile before writing .ecryptfs, as it may be the same\n            rm -f \"$oldkeyfile\"\n            if [ \"$chrootecryptfsfile\" != \"$newkeyfile\" ]; then\n                echo \"$newkeyfile\" > \"$CHROOT/.ecryptfs\"\n            fi\n        elif [ -z \"$ENCRYPT$MOVE\" ]; then\n            echo \"Keyfile is already located at $newkeyfile\" 1>&2\n        fi\n    fi\n\n    # Encrypt/rewrap the chroot\n    if [ -n \"$ENCRYPT\" ]; then\n        # Use mount-chroot to do the heavy lifting\n        unmount=\"sh -e '$BINDIR/unmount-chroot' -y -c '$CHROOTS' -- '$NAME'\"\n        addtrap \"$unmount\"\n        if [ -n \"$KEYFILE\" ]; then\n            sh -e \"$BINDIR/mount-chroot\" -ee -k \"$KEYFILE\" -c \"$CHROOTS\" -- \"$NAME\"\n        else\n            sh -e \"$BINDIR/mount-chroot\" -ee -c \"$CHROOTS\" -- \"$NAME\"\n        fi\n        undotrap\n        eval \"$unmount\"\n    fi\n\n    # Move the chroot if requested\n    if [ -n \"$MOVE\" ]; then\n        target=\"$MOVE\"\n        if [ \"${target##*/}\" = \"$target\" ]; then\n            # No slashes in the path. Assume renaming.\n            if ! validate_name \"$target\"; then\n                error 2 \"Invalid target chroot name '$target'.\"\n            fi\n            target=\"$CHROOTS/$target\"\n        elif [ \"${target%/}\" != \"$target\" ]; then\n            # Ends in a slash; append name.\n            target=\"$target$NAME\"\n        fi\n        if [ -e \"$target\" ]; then\n            # Can't tell if the destination is a directory or a chroot that\n            # already exists; be safe and assume it was a mistake.\n            error 2 \"$target already exists\"\n        fi\n        # Check if we're changing filesystems, because we should cp+rm for\n        # safety. We don't do this when encrypting a chroot (see mount-chroot),\n        # because that would require 2x the space on one device. When switching\n        # filesystems like this, however, that isn't a concern.\n        if [ \"`getmountpoint \"$target\"`\" != \"`getmountpoint \"$CHROOT\"`\" ]; then\n            echo \"Moving $CHROOT across filesystems to $target\" 1>&2\n            echo 'This will take a while.' 1>&2\n            echo \"If the operation gets interrupted, you can safely delete $target\" 1>&2\n            # Confirm long operation\n            if [ \"${YES#[Aa]}\" = \"$YES\" ]; then\n                echo -n \"Are you sure you want to continue? [a/y/N] \" 1>&2\n                if [ -n \"$CROUTON_EDIT_RESPONSE\" ]; then\n                    YES=\"$CROUTON_EDIT_RESPONSE\"\n                    echo \"$YES\" 1>&2\n                else\n                    read -r YES\n                fi\n                if [ \"${YES#[AaYy]}\" = \"$YES\" ]; then\n                     error 2 \"Aborting move of $CHROOT\"\n                fi\n            fi\n            if ! mkdir -p \"`dirname \"$target\"`\"; then\n                error 1 \"Unable to create directory for $target\"\n            fi\n            echo -n \"  Copying to $target...\" 1>&2\n            spinner 200 cp -av --one-file-system \"$CHROOT\" \"$target\"\n            echo \"Finished copying to $target\" 1>&2\n            echo -n \"  Deleting old $CHROOT...\" 1>&2\n            spinner 1000 rm -rfv --one-file-system \"$CHROOT\"\n            echo \"Finished deleting old $CHROOT\" 1>&2\n        else\n            if ! mkdir -p \"`dirname \"$target\"`\"; then\n                error 1 \"Unable to create directory for $target\"\n            fi\n            echo \"Moving $CHROOT to $target\" 1>&2\n            mv \"$CHROOT\" \"$target\"\n        fi\n    fi\n\n    # Copy the chroot if requested\n    if [ -n \"$COPY\" ]; then\n        target=\"$COPY\"\n        if [ \"${target##*/}\" = \"$target\" ]; then\n            # No slashes in the path. Assume same directory.\n            if ! validate_name \"$target\"; then\n                error 2 \"Invalid target chroot name '$target'.\"\n            fi\n            target=\"$CHROOTS/$target\"\n        elif [ \"${target%/}\" != \"$target\" ]; then\n            # Ends in a slash; append name.\n            target=\"$target$NAME\"\n        fi\n        if [ -e \"$target\" ]; then\n            # Can't tell if the destination is a directory or a chroot that\n            # already exists; be safe and assume it was a mistake.\n            error 2 \"$target already exists\"\n        fi\n        if ! mkdir -p \"`dirname \"$target\"`\"; then\n            error 1 \"Unable to create directory for $target\"\n        fi\n        echo \"Copying $CHROOT to $target\" 1>&2\n        spinner 200 cp -av --one-file-system \"$CHROOT\" \"$target\"\n    fi\ndone\n\nexit 0\n"
  },
  {
    "path": "host-bin/enter-chroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nBACKGROUND=''\nBINDIR=\"`dirname \"\\`readlink -f -- \"$0\"\\`\"`\"\nCHROOTS=\"`readlink -m -- \"$BINDIR/../chroots\"`\"\nKEYFILE=''\nLOGIN=''\nNAME=''\nTARGET=''\nUSERNAME='1000'\nTMPXMETHOD=''\nNOLOGIN=''\nSETUPSCRIPT='/prepare.sh'\n\nUSAGE=\"$APPLICATION [options] [command [args...]]\n\nEnters an installed Debian-based chroot for running alongside Chromium OS.\n\nBy default, it will log into the primary user on the first chroot found.\nYou can specify a command and parameters to run instead of an interactive shell.\n\nOptions:\n    -b          Fork and run the specified command silently in the background.\n    -c CHROOTS  Directory the chroots are in. Default: $CHROOTS\n    -l          Make the command part of a login. Parameters are passed directly\n                to the chroot command, and a call to su is appended.\n    -k KEYFILE  Override the auto-detected encryption key location.\n    -n NAME     Name of the chroot to enter. Default: first one found in CHROOTS\n    -t TARGET   Only enter the chroot if it contains the specified TARGET.\n    -u USERNAME Username (or UID) to log into. Default: 1000 (the primary user)\n    -X XMETHOD  Override the auto-detected XMETHOD for this session.\n    -x          Does not log in, but directly executes the command instead.\n                Note that the environment will be empty (sans TERM).\n                Specify -x a second time to run the $SETUPSCRIPT script.\"\n\n# Common functions\n. \"$BINDIR/../installer/functions\"\n\n# Safely launch a command ($*) via /bin/sh within the chroot as the root user.\nchrootcmd() {\n    # env may be overridden when running in the background; don't let it fork.\n    local ret=0 oldtrap=\"$TRAP\"\n    TRAP=''\n    env -i chroot \"$CHROOT\" su -s '/bin/sh' -c \"$*\" - root || ret=$?\n    local pid=\"$!\"\n    # $pid might not be set if env has not been redefined yet\n    if [ -n \"$BACKGROUND\" ] && [ -n \"$pid\" ]; then\n        wait \"$pid\" || ret=$?\n    fi\n    TRAP=\"$oldtrap\"\n    return \"$ret\"\n}\n\n# Process arguments\nprevoptind=1\nwhile getopts 'bc:k:ln:t:u:X:x' f; do\n    # Disallow empty string as option argument\n    if [ \"$((OPTIND-prevoptind))\" = 2 -a -z \"$OPTARG\" ]; then\n        error 2 \"$USAGE\"\n    fi\n    prevoptind=\"$OPTIND\"\n    case \"$f\" in\n    b) BACKGROUND='y';;\n    c) CHROOTS=\"`readlink -m -- \"$OPTARG\"`\";;\n    k) KEYFILE=\"$OPTARG\";;\n    l) LOGIN='y';;\n    n) NAME=\"$OPTARG\";;\n    t) TARGET=\"$OPTARG\";;\n    u) USERNAME=\"$OPTARG\";;\n    X) TMPXMETHOD=\"$OPTARG\";;\n    x) NOLOGIN=\"$((NOLOGIN+1))\"\n       [ \"$NOLOGIN\" -gt 2 ] && NOLOGIN=2;;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\nshift \"$((OPTIND-1))\"\n\n# Shift away empty string as first argument (used in start* scripts to mark\n# the end of user-specified parameters)\nif [ \"$#\" -ge 1 -a -z \"$1\" ]; then\n    shift\nfi\n\n# We need to run as root\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    error 2 \"$APPLICATION must be run as root.\"\nfi\n\n# We need a command if we specified to run in the background\nif [ -n \"$BACKGROUND\" -a $# = 0 ]; then\n    error 2 \"A command must be specified in order to run in the background.\"\nfi\n\n# If -x is specified twice, our command is the setup script.\nif [ \"$NOLOGIN\" = 2 ]; then\n    if [ $# != 0 ]; then\n        error 2 \"A command cannot be specified with -xx.\"\n    fi\n    if [ -n \"$BACKGROUND\" ]; then\n        error 2 \"Cannot run the setup script in the background.\"\n    fi\n    set -- \"$SETUPSCRIPT\"\nfi\n\n# Select the first chroot available if one hasn't been specified\nif [ -z \"$NAME\" ]; then\n    haschroots=''\n    for CHROOT in \"$CHROOTS\"/*; do\n        if [ ! -d \"$CHROOT/etc\" -a ! -f \"$CHROOT/.ecryptfs\" ]; then\n            continue\n        fi\n        haschroots='y'\n        if [ -n \"$TARGET\" ]; then\n            if ! grep -q \"^$TARGET$\" \"$CHROOT/.crouton-targets\" 2>/dev/null; then\n                continue\n            fi\n        fi\n        NAME=\"${CHROOT##*/}\"\n        break\n    done\n    if [ -z \"$haschroots\" ]; then\n        error 1 \"No chroots found in $CHROOTS\"\n    fi\n    if [ -z \"$NAME\" ]; then\n        error 1 \"No chroots with target '$TARGET' found in $CHROOTS\"\n    fi\nelif [ -n \"$TARGET\" ]; then\n    if ! grep -q \"^$TARGET$\" \"$CHROOTS/$NAME/.crouton-targets\" 2>/dev/null; then\n        error 1 \"$CHROOTS/$NAME does not contain target '$TARGET'\"\n    fi\nfi\n\n# Check to ensure that the XMETHOD requested has been installed\nif [ -n \"$TMPXMETHOD\" ]; then\n    if ! grep -q \"^${TMPXMETHOD%%-*}$\" \"$CHROOTS/$NAME/.crouton-targets\"; then\n        error 1 \"$CHROOTS/$NAME does not contain XMETHOD '${TMPXMETHOD%%-*}'\"\n    fi\nfi\n\n# Avoid kernel panics due to slow I/O\ndisablehungtask\n\n# Allow X server running as normal user to set/drop DRM master\ndrm_relax_file=\"/sys/kernel/debug/dri/drm_master_relax\"\nif [ -f \"$drm_relax_file\" ]; then\n    echo 'Y' > \"$drm_relax_file\"\nfi\n\n# Make sure we always exit with echo on the tty.\naddtrap \"stty echo 2>/dev/null\"\n\n# Mount the chroot and update our CHROOT path\nCHROOTSRC=\"$CHROOTS/$NAME\"\nif [ -n \"$KEYFILE\" ]; then\n    CHROOT=\"`sh -e \"$BINDIR/mount-chroot\" \\\n                                     -k \"$KEYFILE\" -p -c \"$CHROOTS\" -- \"$NAME\"`\"\nelse\n    CHROOT=\"`sh -e \"$BINDIR/mount-chroot\" -p -c \"$CHROOTS\" -- \"$NAME\"`\"\nfi\n\nif [ \"$NOLOGIN\" != 2 ]; then\n    echo \"Entering $CHROOTSRC...\" 1>&2\nfi\n\n# In disk full situations, mount-chroot can be empty. Good time to check sanity.\nif [ -z \"$CHROOT\" ]; then\n    error 1 'Something is wrong with the crouton install. Please make sure you have\nsufficient space available, then re-install the chroot and try again.'\nfi\n\n# Register the crash_reporter_wrapper to properly handle coredumps\nif [ -f \"$BINDIR/crash_reporter_wrapper\" ]; then\n    if ! sh -e \"$BINDIR/crash_reporter_wrapper\" register; then\n        echo 'WARNING: Unable to register core dump handler.' 1>&2\n    fi\nfi\n\nif [ -z \"$CROUTON_NO_UNMOUNT\" ]; then\n    # Auto-unmount everything below and including the chroot upon exit\n    addtrap \"sh -e '$BINDIR/unmount-chroot' -yc '$CHROOTS' -- '$NAME'\"\nfi\n\n# If our root is on an external disk we need to ensure USB device persistence is\n# enabled otherwise we will lose the file-system after a suspend event.\nif [ \"${CHROOTSRC#/media}\" != \"$CHROOTSRC\" ]; then\n    for usbp in /sys/bus/usb/devices/*/power/persist; do\n        if [ -e \"$usbp\" ]; then\n            echo 1 > \"$usbp\"\n        fi\n    done\nfi\n\n# Offer to run the setup script if it exists and yet we're logging in.\nif [ -z \"$NOLOGIN\" -a -f \"$CHROOT$SETUPSCRIPT\" ]; then\n    echo 'A chroot setup script still exists inside the chroot.' 1>&2\n    echo 'The chroot may not be fully set up.' 1>&2\n    response=''\n    # A finished setup script will have permissions 500\n    if [ \"`stat -c '%a' \"$CHROOT$SETUPSCRIPT\"`\" != '500' ]; then\n        echo 'However, it appears the setup script is invalid.' 1>&2\n        response='d'\n    fi\n    if [ -t 0 -a -z \"$response\" ]; then\n        echo -n 'Would you like to finish the setup? [Y/n/d] ' 1>&2\n        read -r response\n    fi\n    if [ -z \"$response\" -o \"${response#[Yy]}\" != \"$response\" ]; then\n        echo 'Preparing chroot environment...' 1>&2\n        if CROUTON_NO_UNMOUNT=1 sh -e \"$BINDIR/enter-chroot\" \\\n                -c \"$CHROOTS\" -n \"$NAME\" -xx; then\n            echo 'Setup completed. Entering chroot...' 1>&2\n            response=''\n        else\n            echo 'The chroot setup script may be broken. Your chroot is not fully configured.' 1>&2\n            response='d'\n        fi\n    fi\n    if [ \"${response#[Dd]}\" != \"$response\" ]; then\n        echo 'Removing the chroot setup script. You may want to update your chroot again.' 1>&2\n        rm -f \"$CHROOT$SETUPSCRIPT\"\n    elif [ -n \"$response\" ]; then\n        echo 'Skipping setup. You will be prompted again next time.' 1>&2\n    fi\nfi\n\n# Resolve USERNAME if it is a UID (and we're logging in)\npasswd=\"$CHROOT/etc/passwd\"\nif [ -z \"$NOLOGIN\" ]; then\n    if [ ! -r \"$passwd\" ]; then\n        error 1 \"$CHROOTSRC doesn't appear to be a valid chroot.\"\n    fi\n    case \"$USERNAME\" in\n    ''|*[!0-9]*)\n        # Make sure the username exists\n        if ! grep -q \"^$USERNAME:\" \"$passwd\"; then\n            error 1 \"User $USERNAME not found in $NAME\"\n        fi;;\n     *)\n        # Resolve the UID\n        uid=\"$USERNAME\"\n        USERNAME=\"`awk -F: '$3=='\"$uid\"'{print $1; exit}' \"$passwd\"`\"\n        if [ -z \"$USERNAME\" ]; then\n            error 1 \"UID $uid not found in $NAME\"\n        fi\n    esac\n    # Detect the home directory and shell for the user\n    CHROOTHOME=\"`awk -F: '$1==\"'\"$USERNAME\"'\"{print $6; exit}' \"$passwd\"`\"\n    CHROOTSHELL=\"`awk -F: '$1==\"'\"$USERNAME\"'\"{print $NF; exit}' \"$passwd\"`\"\nelse\n    CHROOTSHELL='/bin/sh'\nfi\n\n# Save the chroot name to the chroot\necho \"$NAME\" > \"$CHROOT/etc/crouton/name\"\n\n# Ensure $CHROOT/var/host exists.\nmkdir -p \"$CHROOT/var/host\"\n\n# Copy in the current Chromium OS version for reference\ncp -f '/etc/lsb-release' \"$CHROOT/var/host/\"\n\n# Copy CRAS version into the chroot\ncras_test_client --version 2>/dev/null > \"$CHROOT/var/host/cras-version\" || true\n\n# Copy the latest Xauthority into the chroot\nif [ -f \"${XAUTHORITY:=/home/chronos/.Xauthority}\" ]; then\n    cp -f \"$XAUTHORITY\" \"$CHROOT/var/host/Xauthority\"\n    chmod 444 \"$CHROOT/var/host/Xauthority\"\n    # Be backwards-compatible, just in case\n    if [ -f \"$CHROOT/etc/X11/host-Xauthority\" ]; then\n        ln -sfT '/var/host/Xauthority' \"$CHROOT/etc/X11/host-Xauthority\"\n    fi\nfi\n\n# Prepare chroot filesystem\n# Soft-link resolv.conf so that updates are automatically propagated\nln -sfT '/var/host/shill/resolv.conf' \"$CHROOT/etc/resolv.conf\"\n\n# Sanity check of the timezone setting\nlocaltime=\"$CHROOT/etc/localtime\"\nhostlocaltime='/var/host/timezone/localtime'\nif [ -h \"$localtime\" ] && [ \"`readlink -- \"$localtime\"`\" = \"$hostlocaltime\" ]; then\n    timezone=\"`readlink -m -- /var/lib/timezone/localtime || true`\"\n    if [ -z \"$timezone\" -o ! -e \"$CHROOT$LOCALTIME\" ]; then\n        echo \"\\\nWARNING: the timezone selected in Chromium OS does not exist inside the chroot.\nTo set the chroot's timezone, run the following: sudo dpkg-reconfigure tzdata\" 1>&2\n    else\n        # Set /etc/timezone in chroot - fixes the clock in Unity\n        echo \"${timezone#/usr/share/zoneinfo/}\" > \"$CHROOT/etc/timezone\"\n    fi\nfi\n\n# Follows and fixes dangerous symlinks, returning the canonicalized path.\nfixabslinks() {\n    local p=\"$CHROOT/$1\" c\n    # Follow and fix dangerous absolute symlinks\n    while c=\"`readlink -m -- \"$p\"`\" && [ \"$c\" != \"$p\" ]; do\n        p=\"$CHROOT${c#\"$CHROOT\"}\"\n    done\n    echo \"$p\"\n}\n\n# Bind-mounts $1 into $CHROOT/${2:-\"$1\"} if $2 is not already mounted\n# If $3 is specified, remounts with the specified options.\n# If $1 starts with a -, it's considered options to the bind mount, and the rest\n# of the parameters are shifted.\nbindmount() {\n    bindopts=''\n    if [ \"${1#\"-\"}\" != \"$1\" ]; then\n        bindopts=\"$1\"\n        shift\n    fi\n    local target=\"`fixabslinks \"${2:-\"$1\"}\"`\"\n    if mountpoint -q \"$target\"; then\n        return 0\n    fi\n    mkdir -p \"$target\"\n    mount --bind $bindopts \"$1\" \"$target\"\n    mount -i -o 'remount,symfollow' \"$target\" 2>/dev/null || true\n    if [ -n \"$3\" ]; then\n        mount -i -o \"remount,$3\" \"$target\"\n    fi\n}\n\n# Creates a tmpfs mount at $CHROOT/$1 with options $2 if not already mounted\ntmpfsmount() {\n    local target=\"`fixabslinks \"$1\"`\"\n    if mountpoint -q \"$target\"; then\n        return 0\n    fi\n    mkdir -p \"$target\"\n    mount -i -t tmpfs -o \"rw${2:+,}$2\" tmpfs \"$target\"\n    mount -i -o 'remount,symfollow' \"$target\" 2>/dev/null || true\n}\n\n# If /var/run isn't mounted, we know the chroot hasn't been started yet.\nif mountpoint -q \"`fixabslinks '/var/run'`\"; then\n    firstrun=''\nelse\n    firstrun='y'\nfi\n\nbindmount /dev\nbindmount /dev/pts\nbindmount /dev/shm\nbindmount /tmp /tmp exec\nbindmount /proc\ntmpfsmount /var/run 'noexec,nosuid,mode=0755,size=10%'\ntmpfsmount /var/run/lock 'noexec,nosuid,nodev,size=5120k'\nbindmount /var/run/dbus /var/host/dbus\nbindmount /var/run/shill /var/host/shill\nbindmount /var/lib/timezone /var/host/timezone\nfor m in /lib/modules/*; do\n    if [ -d \"$m\" ]; then\n        bindmount '-o ro' \"$m\"\n    fi\ndone\n\n# Add a shm symlink to our new /var/run\nln -sfT /dev/shm \"`fixabslinks '/var/run'`/shm\"\n\n# Setup udev control directory in the chroot\n# Chromium OS >=6092 uses /run/udev, older versions /dev/.udev\nif [ -d /var/run/udev ]; then\n    bindmount /var/run/udev /var/host/udev\n    ln -sfT /var/host/udev \"`fixabslinks '/var/run'`/udev\"\nelse\n    # Add a /run/udev symlink for later versions of udev in chroot\n    ln -sfT /dev/.udev \"`fixabslinks '/var/run'`/udev\"\nfi\n\nif [ -d /var/run/cras ]; then\n    bindmount /var/run/cras /var/host/cras\n    # Add a /var/host/cras symlink for CRAS clients\n    ln -sfT /var/host/cras \"$(fixabslinks '/var/run')/cras\"\nelse\n    echo \"\\\nWARNING: CRAS not running in Chromium OS. Audio forwarding will not work.\" 1>&2\nfi\n\nif [ -d /var/run/chrome ]; then\n    bindmount /var/run/chrome /var/host/chrome\n    # Add a /var/host/chrome symlink for display clients\n    ln -sfT /var/host/chrome \"$(fixabslinks '/var/run')/chrome\"\nelse\n    echo \"\\\nWARNING: Chrome not running in Chromium OS. Display forwarding will not work.\" 1>&2\nfi\n\n# Bind-mount /media, specifically the removable directory\ndestmedia=\"`fixabslinks '/var/host/media'`\"\nif ! mountpoint -q \"$destmedia\"; then\n    mount --make-shared /media\n    mkdir -p \"$destmedia\" \"$CHROOT/media\"\n    ln -sfT \"/var/host/media/removable\" \"$CHROOT/media/removable\"\n    mount --rbind /media \"$destmedia\"\nfi\n\n# Provide a default /etc/crouton/shares file\nshares=\"`fixabslinks '/etc/crouton/shares'`\"\nif [ -e \"$shares\" -a ! -f \"$shares\" ]; then\n    echo \"Not mounting shares: /etc/crouton/shares is not a file.\" 1>&2\nelif [ ! -f \"$shares\" ]; then\n    cat > \"$shares\" <<EOF\n# Defines directories to map into the chroot from the host.\n# Order is important: be sure to mount directories before you mount\n# subdirectories of those mounts.\n# The only directories that can be mapped from outside of the chroot are\n# subdirectories of the following locations:\n#  myfiles: ~/MyFiles\n#  downloads: ~/Downloads\n#  shared: /mnt/stateful_partition/crouton/shared\n#  encrypted: ~/crouton/shared\n# Inside the chroot, any absolute or user-relative path (~/x or ~user/x) is OK.\n# Syntax is as follows:\n#  HOSTDIR CHROOTDIR [OPTIONS]\n# Directory names can have spaces (if quoted), but no quote characters or\n# relative path elements (..'s).\n# Options can be any options to mount. If unspecified, \"exec\" is assumed.\n\n# Examples:\n#  Share a home directory encrypted by the current user's unsynced profile\n#   encrypted/home ~\n#  Share an unencrypted directory between chroots.\n#   shared/home ~/Share\n#  Absolute directories on the chroot side work too\n#   shared/bin /usr/local/bin exec,suid\n#  Share a noexec path that has spaces, and a specific user home directory\n#   \"encrypted/stupid example\" \"~omg/Why are you even doing this?\" noexec\n\n# Share the downloads folder of the current user's profile\ndownloads ~/Downloads\nEOF\nfi\n# Bind-mount the stuff in $CHROOT/etc/crouton/shares, unless NOLOGIN is set\nif [ -z \"$NOLOGIN\" -a -f \"$shares\" ]; then\n    localmyfiles='/home/chronos/user/MyFiles'\n    if [ ! -d \"$localmyfiles\" ]; then\n        localmyfiles=''\n    fi\n    localdownloads='/home/chronos/user/Downloads'\n    if [ ! -d \"$localdownloads\" ]; then\n        localdownloads=''\n    fi\n    localencrypted='/home/chronos/user/crouton/shared'\n    if [ ! -d \"/home/chronos/user\" ]; then\n        localencrypted=''\n    fi\n    localshare='/mnt/stateful_partition/crouton/shared'\n    # Parse file and split into three lines: src, dest, options\n    awk '\n        BEGIN {\n            PARAMMATCH = \"^[ \\t]*(\\\"[^\\\"]+\\\"|[^ \\t\\\"]+)([ \\t]\"\n        }\n        function strip(s) {\n            if (index(s, \"\\\"\")) {\n                sub(\"[ \\t]*\\\"\", \"\", s)\n                sub(\"\\\"[ \\t]*\", \"\", s)\n            } else {\n                gsub(\"[ \\t]\", \"\", s)\n            }\n            return s\n        }\n        $1 ~ /^#/ { next }\n        match($0, PARAMMATCH \")\") {\n            dest = substr($0, RLENGTH+1)\n            src = strip(substr($0, 1, RLENGTH))\n            if (match(dest, PARAMMATCH \"|$)\")) {\n                opts = strip(substr(dest, RLENGTH+1))\n                dest = strip(substr(dest, 1, RLENGTH))\n                if (src && dest ~ \"^[/~]\" && opts ~ \"^[a-z,]*$\") {\n                    # Ensure that they end in slashes\n                    if (src !~ \"/$\")\n                        src = src \"/\"\n                    if (dest !~ \"/$\")\n                        dest = dest \"/\"\n                    # .. path elements are not allowed\n                    if (src !~ \"/../\" && dest !~ \"/../\") {\n                        print src\n                        print dest\n                        print opts\n                        next\n                    }\n                }\n            }\n        }\n        /./ {\n            print \"error\"\n            print \"Invalid syntax in /etc/crouton/shares on line \" NR \":\\n  \" \\\n                  $0\n        }\n    ' \"$shares\" | while read -r src && read -r dest && read -r opts; do\n        line=\"\\n  \\\"$src\\\" \\\"$dest\\\" $opts\"\n        # Expand src\n        case \"${src%%/*}\" in\n            myfile|myfiles)\n                if [ -z \"$localmyfiles\" ]; then\n                    echo \"Not mounting share (no Chromium OS user):$line\" 1>&2\n                    continue\n                fi\n                src=\"$localmyfiles/${src#*/}\";;\n            download|downloads)\n                if [ -z \"$localdownloads\" ]; then\n                    echo \"Not mounting share (no Chromium OS user):$line\" 1>&2\n                    continue\n                fi\n                src=\"$localdownloads/${src#*/}\";;\n            encrypt|encrypted)\n                if [ -z \"$localencrypted\" ]; then\n                    echo \"Not mounting share (no Chromium OS user):$line\" 1>&2\n                    continue\n                fi\n                src=\"$localencrypted/${src#*/}\";;\n            share|shares|shared)\n                src=\"$localshare/${src#*/}\";;\n            error)\n                # Print the error message from awk script.\n                echo \"$dest\" 1>&2\n                echo \"$opts\" 1>&2\n                continue;;\n            *)\n                echo \"Invalid share:$line\" 1>&2\n                continue;;\n        esac\n        # Expand dest for homedirs\n        if [ \"${dest#\"~\"}\" != \"$dest\" ]; then\n            destuser=\"${dest%%/*}\"\n            if [ \"$destuser\" = '~' ]; then\n                if [ -z \"$CHROOTHOME\" ]; then\n                    echo \"Not mounting share (no chroot user):$line\" 1>&2\n                    continue\n                fi\n                dest=\"$CHROOTHOME/${dest#*/}\"\n            else\n                dest=\"/home/${destuser#\"~\"}/${dest#*/}\"\n            fi\n        fi\n        # Do the bindmount\n        mkdir -m 700 -p \"$src\"\n        if ! bindmount \"$src\" \"$dest\" \"${opts:-exec}\"; then\n            echo \"Failed to mount share:$line\" 1>&2\n        fi\n    done\nfi\n\n# Bind-mount /sys recursively, making it a slave in the chroot\nif ! mountpoint -q \"$CHROOT/sys\"; then\n    mkdir -p \"$CHROOT/sys\"\n    mount --make-rshared /sys\n    mount --rbind /sys \"$CHROOT/sys\"\n    mount --make-rslave \"$CHROOT/sys\"\n    # Unmount selinux in the chroot, make a fake entry to set enforce=0\n    if mountpoint -q \"$CHROOT/sys/fs/selinux\"; then\n        umount \"$CHROOT/sys/fs/selinux\"\n        mount -t tmpfs none \"$CHROOT/sys/fs/selinux\"\n        echo 0 > \"$CHROOT/sys/fs/selinux/enforce\"\n        mount -o remount,ro \"$CHROOT/sys/fs/selinux\"\n    fi\nfi\n\n# Modify chroot's /sys/class/drm and /dev/dri to avoid vgem/mfgsys\nvarrundrm=\"$(fixabslinks '/var/run/drm')\"\nvarrundri=\"$(fixabslinks '/var/run/dri')\"\nsysclassdrm=\"$(fixabslinks '/sys/class/drm')\"\ndevdri=\"$(fixabslinks '/dev/dri')\"\nif [ ! -d \"$varrundrm\" -a -d \"$sysclassdrm\" -a -d \"$devdri\" ]; then\n    cp -Ta \"$sysclassdrm\" \"$varrundrm\"\n    cp -Ta \"$devdri\" \"$varrundri\"\n    for f in \"$varrundrm\"/*; do\n        if [ -h \"$f\" ] && readlink -- \"$f\" | grep -qF -e /vgem/ -e mfgsys; then\n            rm -f \"$f\" \"$varrundri/${f##*/}\"\n        fi\n    done\n    # Scanning of /dev/dri is done sequentially, so make sure there's a card0\n    for f in \"$varrundri/card\"*; do\n        [ -e \"$varrundri/card0\" ] || mv -f \"$f\" \"$varrundri/card0\"\n    done\n    mount --bind \"$varrundrm\" \"$sysclassdrm\"\n    mount --bind \"$varrundri\" \"$devdri\"\nfi\n\n# Get croutonversion variables\ncroutonversion=\"$CHROOT/usr/local/bin/croutonversion\"\nCHROOTRELEASE=\"unknown\"\nif [ -x \"$croutonversion\" ]; then\n    CHROOTRELEASE=\"`\"$croutonversion\" -r 2>/dev/null || echo \"$CHROOTRELEASE\"`\"\nfi\n\n# For test machines with low entropy, bind mount /dev/urandom to /dev/random\nif [ -n \"$CROUTON_WEAK_RANDOM\" ]; then\n    mount --bind \"$CHROOT/dev/urandom\" \"$CHROOT/dev/random\"\nfi\n\n# Fix group numbers for critical groups to match Chromium OS. This is necessary\n# so that users have access to shared hardware, such as video and audio.\ngfile=\"$CHROOT/etc/group\"\nif [ -f \"$gfile\" ]; then\n    for group in audio:hwaudio cras:audio cdrom chronos-access:crouton \\\n                 devbroker-access dialout disk floppy i2c input lp serial \\\n                 tape tty usb:plugdev uucp video wayland; do\n        hostgroup=\"${group%:*}\"\n        chrootgroup=\"${group#*:}\"\n        gid=\"$(awk -F: '$1==\"'\"$hostgroup\"'\"{print $3; exit}' '/etc/group')\"\n        curgid=\"$(awk -F: '$1==\"'\"$chrootgroup\"'\"{print $3; exit}' \"$gfile\")\"\n        if [ -z \"$gid\" ]; then\n            if [ -z \"$curgid\" ]; then\n                echo \"Creating unassociated $chrootgroup group...\" 1>&2\n                chrootcmd groupadd --system \"$chrootgroup\"\n            fi\n            continue\n        fi\n        if [ \"$gid\" = \"$curgid\" ]; then\n            continue\n        elif [ -z \"$curgid\" ]; then\n            echo \"Creating $chrootgroup group with GID $gid...\" 1>&2\n            groupcmd=groupadd\n        else\n            echo \"Changing $chrootgroup GID from $curgid to $gid...\" 1>&2\n            groupcmd=groupmod\n        fi\n        move=\"`awk -F: '$3=='\"$gid\"'{print $1; exit}' \"$gfile\"`\"\n        if [ -n \"$move\" ]; then\n            ngid=\"$gid\"\n            while grep -q \":$ngid:\" \"$gfile\"; do\n                ngid=\"$((ngid+1))\"\n            done\n            echo \"Moving $move GID from $gid to $ngid...\" 1>&2\n            chrootcmd groupmod -g \"$ngid\" \"$move\"\n        fi\n        chrootcmd \"$groupcmd\" -g \"$gid\" \"$chrootgroup\"\n    done\nfi\n\n# To run silently, we override the env command to launch a background process,\n# and move the trap code to happen there.\nif [ -n \"$BACKGROUND\" ]; then\n    env() {\n        # Shuffle FDs around to preserve stdin\n        { (\n            trap '' INT HUP\n            trap \"$TRAP\" 0\n            exec 0<&9 9<&-\n            [ -t 0 ] && exec < /dev/null\n            [ -t 1 ] && exec > /dev/null\n            [ -t 2 ] && exec 2>&1\n            /usr/bin/env \"$@\"\n        ) & } 9<&0\n    }\nfi\n\nret=0\n\n# Launch the system dbus unless we are entering a basic shell.\nif [ \"$NOLOGIN\" != 1 ] && grep -q '^root:' \"$passwd\" 2>/dev/null; then\n    # Try to detect the dbus user by parsing its configuration file\n    # If it fails, or if the user does not exist, `id -un '$dbususer'`\n    # will fail, and we fallback on a default user name (\"messagebus\")\n    dbususer=\"`echo \"cat /busconfig/user/text()\" \\\n        | xmllint --shell \"$CHROOT/etc/dbus-1/system.conf\" 2>/dev/null \\\n        | grep '^[a-z][-a-z0-9_]*$' || true`\"\n    chrootcmd \"\n        if ! hash dbus-daemon 2>/dev/null; then\n            exit 0\n        fi\n        dbususer='$dbususer'\"'\n        pidfile=\"/var/run/dbus/pid\"\n        if [ -f \"$pidfile\" ]; then\n            if grep -q \"^dbus-daemon\" \"/proc/`cat \"$pidfile\"`/cmdline\" \\\n                    2>/dev/null; then\n                exit 0\n            fi\n            rm -f \"$pidfile\"\n        fi\n        mkdir -p /var/run/dbus\n        dbususer=\"`id -un \"$dbususer\" 2>/dev/null || echo \"messagebus\"`\"\n        dbusgrp=\"`id -gn \"$dbususer\" 2>/dev/null || echo \"messagebus\"`\"\n        chown \"$dbususer:$dbusgrp\" /var/run/dbus\n        exec dbus-daemon --system --fork' || ret=$?\n    if [ \"$ret\" != 0 ]; then\n        echo \"WARNING: starting chroot system dbus daemon failed with code $ret\" 1>&2\n        ret=0\n    fi\n\n    # Launch systemd-logind if available and not already running\n    # Whitelisted for saucy and trusty\n    systemd_dir=\"`fixabslinks '/run/systemd'`\"\n    if [ -x \"$CHROOT/lib/systemd/systemd-logind\" ] && \\\n            [ ! -d \"$systemd_dir\" ] && \\\n            [ \"$CHROOTRELEASE\" = 'saucy' -o \"$CHROOTRELEASE\" = 'trusty' ]; then\n        # Every piece of systemd code ever assumes that this directory exists\n        mkdir -p \"$systemd_dir\"\n\n        # Create systemd cgroup if necessary\n        if ! mountpoint -q \"$CHROOT/sys/fs/cgroup/systemd\"; then\n            mkdir -p \"$CHROOT/sys/fs/cgroup/systemd\"\n            mount -t cgroup -o nosuid,noexec,nodev,none,name=systemd systemd \\\n                  \"$CHROOT/sys/fs/cgroup/systemd\"\n        fi\n        # systemd-logind doesn't fork\n        chrootcmd \"/lib/systemd/systemd-logind >/dev/null 2>&1 </dev/null &\"\n    fi\n\n    chrootcmd \"/usr/local/bin/croutonfindnacld >/dev/null 2>&1 </dev/null &\"\nfi\n\n# Start the chroot and any specified command\nif [ -n \"$NOLOGIN\" ]; then\n    env -i TERM=\"$TERM\" chroot \"$CHROOT\" \"$@\" || ret=$?\n    # Handle the return value when running the setup script.\n    if [ \"$NOLOGIN\" = 2 ]; then\n        # If it succeeded yet still exists, run it again.\n        if [ \"$ret\" = 0 -a -f \"$CHROOT$SETUPSCRIPT\" ]; then\n            sh -e \"$BINDIR/unmount-chroot\" -yxc \"$CHROOTS\" -- \"$NAME\"\n            # We don't need to return from this.\n            exec sh -e \"$BINDIR/enter-chroot\" -c \"$CHROOTS\" -n \"$NAME\" -xx\n        elif [ \"$ret\" != 0 ]; then\n            error \"$ret\" 'Failed to complete chroot setup.'\n        fi\n    fi\nelse\n    # Check and run rc.local\n    if [ -n \"$firstrun\" -a -x \"$CHROOT/etc/rc.local\" ]; then\n        ( sleep 10; echo \"Still running chroot's rc.local...\" 2>&1 ) &\n        noticepid=\"$!\"\n        addtrap \"kill '$noticepid' 2>/dev/null\"\n        chrootcmd 'exec /etc/rc.local >/dev/null 2>/dev/null </dev/null' \\\n            || ret=$?\n        undotrap\n        kill \"$noticepid\" 2>/dev/null || true\n        wait \"$noticepid\" || true\n        if [ \"$ret\" != 0 ]; then\n            echo \"WARNING: /etc/rc.local failed with code $ret\" 1>&2\n            ret=0\n        fi\n    fi\n\n    if [ $# = 0 -o -n \"$LOGIN\" ]; then\n        env -i TERM=\"$TERM\" chroot \"$CHROOT\" \"$@\" su - \"$USERNAME\" || ret=$?\n    else\n        # Escape out the command\n        cmd=\"export SHELL='$CHROOTSHELL';\"\n        if [ -n \"$TMPXMETHOD\" ]; then\n            cmd=\"$cmd export XMETHOD='$TMPXMETHOD';\"\n        fi\n        for param in \"$@\"; do\n            cmd=\"$cmd'`echo -n \"$param\" | sed \"s/'/'\\\\\\\\\\\\''/g\"`' \"\n        done\n        env -i TERM=\"$TERM\" chroot \"$CHROOT\" \\\n            su -s '/bin/sh' -c \"$cmd\" - \"$USERNAME\" \\\n            || ret=$?\n    fi\nfi\n\n# We don't want to trap for this proccess if we're running in the background\nif [ -n \"$BACKGROUND\" ]; then\n    settrap ''\nfi\n\n# Cleanup all happens in the exit trap\nexit $ret\n"
  },
  {
    "path": "host-bin/mount-chroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nBINDIR=\"`dirname \"\\`readlink -f -- \"$0\"\\`\"`\"\nCHROOTS=\"`readlink -m -- \"$BINDIR/../chroots\"`\"\nCREATE=''\nENCRYPT=''\nKEYFILE=''\nPRINT=''\nROOT=\"`readlink -m -- '/var/run/crouton'`\"\nMOUNTOPTS='rw,dev,exec,suid'\nMETRICSDIR='/run/metrics/external/crouton'\n\nUSAGE=\"$APPLICATION [options] name [...]\n\nMounts one or more chroots into a root-only subdirectory of $ROOT\n\nOptions:\n    -c CHROOTS  Directory the chroots are in. Default: $CHROOTS\n    -e          If the chroot is not encrypted, encrypt it.\n                If specified twice, prompt to change the encryption passphrase.\n    -k KEYFILE  File or directory to store the (encrypted) encryption keys in.\n                If unspecified, the keys will be stored in the chroot if doing a\n                first encryption, or auto-detected on existing chroots.\n    -n          Create the chroot if it doesn't exist.\n    -p          Prints out the path to the mounted directory on stdout.\"\n\n# Common functions\n. \"$BINDIR/../installer/functions\"\n\n# Process arguments\ngetopts_string='c:ek:np'\nwhile getopts_nextarg; do\n    case \"$getopts_var\" in\n    c) CHROOTS=\"`readlink -m -- \"$getopts_arg\"`\";;\n    e) ENCRYPT=\"$((ENCRYPT+1))\";;\n    k) KEYFILE=\"$getopts_arg\";;\n    n) CREATE='y';;\n    p) PRINT='y';;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\n\n# Need at least one chroot listed\nif [ $# = 0 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# We need to run as root\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    error 2 \"$APPLICATION must be run as root.\"\nfi\n\n# Make sure we always exit with echo on the tty.\naddtrap \"stty echo 2>/dev/null\"\n\n# Whitelists a directory for symlink and other hardening\nwhitelist() {\n    # As of 67, symlinks and fifos are blocked in stateful partitions.\n    # Add exceptions for this chroot.\n    local sec='/sys/kernel/security' mounted=''\n    if mountpoint -q \"$sec\"; then\n        mounted=y\n    elif ! mount -n -t securityfs -o nodev,noexec,nosuid securityfs \"$sec\"; then\n        return\n    fi\n    # Ensure it's mounted rw\n    if ! mount -o remount,rw \"$sec\"; then\n        echo \"Failed to make inode security policies writeable\" >&2\n        return 1\n    fi\n    policies=\"$sec/chromiumos/inode_security_policies\"\n    if [ -d \"$policies\" ]; then\n        # Touch allow_symlink first to avoid kernel crash on chromeos-5.15 R126.\n        printf \"$CHROOT\" > \"$policies/allow_symlink\"\n        printf \"$CHROOT\" > \"$policies/allow_fifo\"\n    fi\n    if [ -z \"$mounted\" ]; then\n        umount \"$sec\"\n    fi\n}\n\n# Function to prompt the user for a passphrase. Sets $passphrase.\npromptNewPassphrase() {\n    echo_tty -n \"Choose an encryption passphrase for $NAME: \"\n    [ -t 0 ] && stty -echo\n    while [ -z \"$passphrase\" ]; do\n        read -r passphrase\n        if [ -z \"$passphrase\" ]; then\n            echo_tty ''\n            echo_tty -n 'You must specify a passphrase: '\n            continue\n        fi\n        echo_tty ''\n        echo_tty -n 'Please confirm your passphrase: '\n        read -r confirmation\n        if [ \"$confirmation\" != \"$passphrase\" ]; then\n            passphrase=''\n            echo_tty ''\n            echo_tty -n 'Passphrases do not match; try again: '\n        fi\n        confirmation=''\n    done\n    [ -t 0 ] && stty echo\n    echo_tty ''\n}\n\n# Mount each chroot\nfor NAME in \"$@\"; do\n    if ! validate_name \"$NAME\"; then\n        error 2 \"Invalid chroot name '$NAME'.\"\n    fi\n\n    # Check for existence\n    CHROOT=\"$CHROOTS/$NAME\"\n    movesrc=''\n    if [ -d \"$CHROOT\" ]; then\n        if [ -f \"$CHROOT/.ecryptfs\" -o -n \"$ENCRYPT\" ]; then\n            if [ -z \"$ENCRYPT\" ]; then\n                ENCRYPT=0\n            fi\n            # Check for non-encrypted files that we may need to move\n            for file in \"$CHROOT/\"*; do\n                if [ \"${file#*/ECRYPTFS_FNEK_ENCRYPTED}\" = \"$file\" ]; then\n                    movesrc=\"$CHROOT\"\n                    break\n                fi\n            done\n        fi\n    elif [ -z \"$CREATE\" ]; then\n        error 1 \"$CHROOT not found.\"\n    else\n        mkdir -p \"$CHROOT\"\n    fi\n\n    CHROOTSRC=\"$CHROOT\"\n    CHROOT=\"$ROOT/${CHROOT#/}\"\n\n    # Ensure there's a root-only folder for the bind-mounted chroot\n    mkdir -p -m 0700 \"$CHROOT\"\n    chown root:root \"$ROOT\"\n    chmod 700 \"$ROOT\"\n\n    # Extraordinarily vague usage stat; see https://crbug.com/989219\n    if [ -z \"${CROUTON_DISABLE_STATS-}\" -a -d \"${METRICSDIR%/*}\" ]; then\n        mkdir -p -m 0777 \"$METRICSDIR\"\n        chown root:root \"$METRICSDIR\"\n        chmod 777 \"$METRICSDIR\"\n        touch \"$METRICSDIR/crouton-started\"\n    fi || true\n\n    if [ -n \"$PRINT\" ]; then\n        echo \"$CHROOT\"\n    fi\n\n    # Check if we actually need to mount\n    if ! mountpoint -q \"$CHROOT\"; then\n        if [ -z \"$ENCRYPT\" ]; then\n            mount --bind \"$CHROOTSRC\" \"$CHROOT\"\n            mount -i -o \"remount,$MOUNTOPTS\" \"$CHROOT\"\n            mount -i -o 'remount,symfollow' \"$CHROOT\" 2>/dev/null || true\n            mount --make-private \"$CHROOT\"\n            whitelist \"$CHROOT\"\n            continue\n        fi\n\n        # We must be on a terminal, unless we already have a password in env.\n        if [ ! -t 0 -a -z \"$CROUTON_PASSPHRASE$CROUTON_NEW_PASSPHRASE\" ]; then\n            error 2 'STDIN is not a terminal; cannot request passwords.'\n        fi\n\n        # Ensure that there's a root password set before decrypting the chroot,\n        # unless the passphrase was specified via env, which isn't secure anyway\n        if [ ! -f '/mnt/stateful_partition/etc/devmode.passwd' ]; then\n            echo_tty \\\n'You must have a root password in Chromium OS to mount encrypted chroots.'\n            if [ -z \"$CROUTON_PASSPHRASE$CROUTON_NEW_PASSPHRASE\" ]; then\n                while ! chromeos-setdevpasswd; do :; done\n            fi\n        fi\n\n        # Detect the key file\n        if [ -z \"$KEYFILE\" ]; then\n            KEYFILE=\"$CHROOTSRC/.ecryptfs\"\n            if [ -f \"$KEYFILE\" ]; then\n                header=\"`head -n1 \"$KEYFILE\"`\"\n                if [ -n \"$header\" ]; then\n                    KEYFILE=\"$header\"\n                fi\n            fi\n        elif [ \"${KEYFILE#/}\" = \"$KEYFILE\" ]; then\n            KEYFILE=\"$PWD/$KEYFILE\"\n        fi\n        if [ -d \"$KEYFILE\" -o \"${KEYFILE%/}\" != \"$KEYFILE\" ]; then\n            KEYFILE=\"${KEYFILE%/}/$NAME\"\n        fi\n        if ! mkdir -p \"`dirname \"$KEYFILE\"`\"; then\n            error 1 \"Unable to create directory for $KEYFILE\"\n        fi\n\n        # If we just created it, choose and create the keyfile.\n        passphrase=\"${CROUTON_PASSPHRASE:-\"$CROUTON_NEW_PASSPHRASE\"}\"\n        if [ ! -f \"$CHROOTSRC/.ecryptfs\" ]; then\n            if [ -e \"$KEYFILE\" ]; then\n                error 1 \"Encryption key file $KEYFILE already exists. Refusing to overwrite!\"\n            fi\n\n            promptNewPassphrase\n\n            if [ -z \"$CROUTON_WEAK_RANDOM\" ]; then\n                random=\"/dev/random\"\n                echo 'Generating keys (move the mouse to generate entropy)...' 1>&2\n            else\n                random=\"/dev/urandom\"\n                echo 'Generating keys from /dev/urandom...' 1>&2\n            fi\n            key=\"`hexdump -v -n32 -e'32/1 \"%02x\"' \"$random\"`\"\n            fnek=\"`hexdump -v -n32 -e'32/1 \"%02x\"' \"$random\"`\"\n            echo 'done' 1>&2\n\n            # Create key file\n            wrappedkey=\"`mktemp`\"\n            wrappedfnek=\"`mktemp`\"\n            addtrap \"rm -f '$wrappedkey' '$wrappedfnek'\"\n            echo -n \"$key\n$passphrase\" | ecryptfs-wrap-passphrase \"$wrappedkey\" -\n            echo -n \"$fnek\n$passphrase\" | ecryptfs-wrap-passphrase \"$wrappedfnek\" -\n            unset key fnek\n            echo | cat - \"$wrappedkey\" \"$wrappedfnek\" > \"$KEYFILE\"\n            if [ ! -f \"$CHROOTSRC/.ecryptfs\" ]; then\n                echo \"$KEYFILE\" > \"$CHROOTSRC/.ecryptfs\"\n            fi\n        elif [ ! -f \"$KEYFILE\" ]; then\n            error 1 \"Unable to find encryption key file $KEYFILE\"\n        else\n            echo_tty -n \"Enter encryption passphrase for $NAME: \"\n            [ -t 0 ] && stty -echo\n            if [ -z \"$passphrase\" ]; then\n                read -r passphrase\n            fi\n            [ -t 0 ] && stty echo\n            echo_tty ''\n\n            wrappedkey=\"`mktemp`\"\n            wrappedfnek=\"`mktemp`\"\n            addtrap \"rm -f '$wrappedkey' '$wrappedfnek'\"\n\n            # Extract wrapped keys from keyfile\n            wrappedtotal=\"$(($(wc -c < \"$KEYFILE\") - $(head -n 1 \"$KEYFILE\" | wc -c)))\"\n            wrappedsize=\"$((wrappedtotal / 2))\"\n            tail -c \"$wrappedtotal\" \"$KEYFILE\" | head -c \"$wrappedsize\" > \"$wrappedkey\"\n            tail -c \"$wrappedsize\" \"$KEYFILE\" > \"$wrappedfnek\"\n\n            # Change the passphrase if requested\n            if [ \"${ENCRYPT:-0}\" -ge 2 ]; then\n                oldpassphrase=\"$passphrase\"\n                passphrase=\"$CROUTON_NEW_PASSPHRASE\"\n                promptNewPassphrase\n\n                echo \"Applying passphrase change\" 1>&2\n                echo -n \"$oldpassphrase\n$passphrase\" | ecryptfs-rewrap-passphrase \"$wrappedkey\" -\n                echo -n \"$oldpassphrase\n$passphrase\" | ecryptfs-rewrap-passphrase \"$wrappedfnek\" -\n                echo | cat - \"$wrappedkey\" \"$wrappedfnek\" > \"$KEYFILE\"\n\n                unset oldpassphrase\n            fi\n        fi\n\n        # Add keys to keychain and extract\n        keysig=\"`echo -n \"$passphrase\" \\\n            | ecryptfs-unwrap-passphrase \"$wrappedkey\" - 2>/dev/null \\\n            | ecryptfs-add-passphrase - 2>/dev/null \\\n            | sed -n 's/.*\\[\\([0-9a-zA-Z]*\\)\\].*/\\1/p'`\"\n        fneksig=\"`echo -n \"$passphrase\" \\\n            | ecryptfs-unwrap-passphrase \"$wrappedfnek\" - 2>/dev/null \\\n            | ecryptfs-add-passphrase - 2>/dev/null \\\n            | sed -n 's/.*\\[\\([0-9a-zA-Z]*\\)\\].*/\\1/p'`\"\n        if [ -z \"$keysig\" -o -z \"$fneksig\" ]; then\n            error 1 \"Failed to decrypt $NAME.\"\n        fi\n\n        # Create a new session, and link user keyring to that session,\n        # as required by ecryptfs.\n        keyctl new_session >/dev/null\n        keyctl link @u @s\n\n        mnt=\"ecryptfs_sig=$keysig,ecryptfs_fnek_sig=$fneksig\"\n        mnt=\"$mnt,ecryptfs_cipher=aes,ecryptfs_key_bytes=16\"\n        mnt=\"$mnt,ecryptfs_unlink_sigs,$MOUNTOPTS\"\n\n        if ! mount -i -t ecryptfs -o \"$mnt\" \"$CHROOTSRC\" \"$CHROOT\"; then\n            error 1 \"Failed to mount $NAME.\"\n        fi\n\n        mount -i -o 'remount,symfollow' \"$CHROOT\" 2>/dev/null || true\n\n        whitelist \"$CHROOT\"\n    fi\n\n    # Perform the move\n    if [ -z \"$movesrc\" ]; then\n        continue\n    fi\n    response=y\n    for file in \"$movesrc/\"*; do\n        if [ \"${file#*/ECRYPTFS_FNEK_ENCRYPTED}\" != \"$file\" ]; then\n            echo -n \\\n\"About to continue encrypting the unencrypted portion of $NAME.\nIf this is unexpected, then it could mean that someone's trying to inject files\ninto your encrypted chroot, potentially allowing them to steal your data.\nPlease choose one of the following options:\nyes  -- You are sure you want to continue moving the files in. They're yours.\ndel  -- You do not like these files and want them deleted permanently.\nlist -- You do not know what these files are and want to list them.\nno   -- You don't want to decide one way or another quite yet.\n> \" 1>&2\n            # Don't allow a response to be specified in env unless the password\n            # was also specified in env.\n            if [ -n \"$CROUTON_PASSPHRASE\" -a \\\n                    -n \"$CROUTON_MOUNT_RESPONSE\" ]; then\n                response=\"$CROUTON_MOUNT_RESPONSE\"\n                echo \"$response\" 1>&2\n            else\n                read -r response\n            fi\n            break\n        fi\n    done\n    case \"$response\" in\n    y*|Y*) (\n        echo -n \"Encrypting $NAME; please wait...\" 1>&2\n        cd \"$movesrc\"\n        tmp=\"`mktemp -d --tmpdir=. 'ECRYPTFS_MOVE_STAGING_XXXXXX'`\"\n        find -not -name 'ECRYPTFS_FNEK_ENCRYPTED*' \\\n             -not -wholename './ECRYPTFS_MOVE_STAGING_*' \\\n             -not -wholename '.' \\\n             -not -wholename './.ecryptfs' \\\n             -not -wholename './.crouton-targets' \\\n             -exec mkdir -p \"$tmp/{}\" ';' \\\n             -exec rmdir \"$tmp/{}\" ';' \\\n             '(' -prune , -exec mv -fT '{}' \"$tmp/{}\" ';' ')' 1>&2\n        for tmp in ECRYPTFS_MOVE_STAGING_*; do\n            (\n                cd \"$tmp\"\n                find '!' '(' -type d -exec test -d \"$CHROOT/{}\" ';' ')' \\\n                     '(' -prune , -exec mv -fT '{}' \"$CHROOT/{}\" ';' ')' \\\n                     -exec echo -n . ';' 1>&2\n                find -depth -type d -not -wholename . \\\n                     -exec test -d \"$CHROOT/{}\" ';' \\\n                     -exec rmdir '{}' ';' 1>&2\n            )\n            rmdir \"$tmp\" 2>/dev/null || true\n        done\n        echo 'done.' 1>&2\n    );;\n    d*|D*) (\n        echo \"Deleting unencrypted files in $NAME; please wait...\" 1>&2\n        cd \"$movesrc\"\n        find -not -name 'ECRYPTFS_FNEK_ENCRYPTED*' \\\n             -not -wholename '.' \\\n             -not -wholename './.ecryptfs' \\\n             -not -wholename './.crouton-targets' \\\n             '(' -prune , -exec rm -rvf '{}' ';' ')' 1>&2\n        echo 'Done.' 1>&2\n    );;\n    l*|L*) (\n        echo \"Listing unencrypted files in $NAME; please wait...\" 1>&2\n        cd \"$movesrc\"\n        find -not -name 'ECRYPTFS_FNEK_ENCRYPTED*' \\\n             -not -name 'ECRYPTFS_MOVE_STAGING_*' \\\n             -not -wholename '.' \\\n             -not -wholename './.ecryptfs' \\\n             -not -wholename './.crouton-targets' \\\n             -print -prune | cut -b2- 1>&2\n        echo 'Done.' 1>&2\n        exit 2\n    );;\n    esac\ndone\n\nexit 0\n"
  },
  {
    "path": "host-bin/startcli",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start a CLI session in a new VT.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nif [ -f /sbin/frecon ]; then\n    # Until we get a stable interface to controlling frecon, just use the pty\n    openvt=''\nelse\n    openvt='openvt -vws --'\nfi\n\nexport TERM='linux'\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t cli-extra -l \\\n    \"$@\" \"\" $openvt\n"
  },
  {
    "path": "host-bin/starte17",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start an e17 session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t e17 \"$@\" \"\" \\\n    exec xinit /usr/bin/enlightenment_start\n"
  },
  {
    "path": "host-bin/startgnome",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start a GNOME session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t gnome \"$@\" \"\" \\\n    exec startgnome\n"
  },
  {
    "path": "host-bin/startkde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start a KDE session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t kde \"$@\" \"\" \\\n    exec xinit /usr/bin/startkde\n"
  },
  {
    "path": "host-bin/startkodi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start an KODI session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t kodi \"$@\" \"\" \\\n    exec croutonpowerd -i xinit /usr/bin/kodi --standalone\n"
  },
  {
    "path": "host-bin/startlxde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start an LXDE session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t lxde \"$@\" \"\" \\\n    exec xinit /usr/bin/startlxde\n"
  },
  {
    "path": "host-bin/startunity",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start a Unity session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t unity \"$@\" \"\" \\\n    exec startunity\n"
  },
  {
    "path": "host-bin/startxfce4",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\n\nUSAGE=\"$APPLICATION [options]\n\nWraps enter-chroot to start an Xfce session.\nBy default, it will log into the primary user on the first chroot found.\n\nOptions are directly passed to enter-chroot; run enter-chroot to list them.\"\n\nexec sh -e \"`dirname \"\\`readlink -f -- \"$0\"\\`\"`/enter-chroot\" -t xfce \"$@\" \"\" \\\n    exec startxfce4\n"
  },
  {
    "path": "host-bin/startxiwi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nENTERCHROOT=\"$(dirname \"$(readlink -f -- \"$0\")\")/enter-chroot\"\nOPTS_ENTER=''\nOPTS_XIWI=''\n\nUSAGE=\"$APPLICATION [options] chroot_app [parameters]\n\nWraps enter-chroot to launch a window or tab in Chromium OS for any graphical application.\nApplications launched in this way show in independent windows or tabs.\n\nBy default, it will use the primary user on the first xiwi-enabled chroot found and launch\nthe chroot_app in a window.\n\nOptions:\n$(\"$ENTERCHROOT\" -h 2>&1 | grep -e ' -[bckntu]')\n    -F          Launch the chroot_app full-screen.\n    -T          Launch the chroot_app in a tab.\n    -f          Prevent xiwi from quitting automatically. (see NOTE below)\n\nNOTE:\nxiwi will normally close when the application returns. Some gui applications\nfork before or during normal operation, which can confuse xiwi and cause it to\nquit prematurely. If your application does not have a parameter that prevents\nit from forking, and crouton is unable to automatically detect the fork, you can\nuse -f to prevent xiwi from quitting automatically.\nxiwi will quit if you close the Chromium OS window when nothing is displayed.\n  \nYou can cycle through multiple windows inside the application\nvia Ctrl-Alt-Tab/Ctrl-Alt-Shift-Tab, or close them via Ctrl-Alt-Shift-Escape.\nIf the chroot_app begins with 'start' but you still want to\nuse the default window manager, specify the full path of the application.\n\"\n\nwhile getopts 'bc:k:n:t:u:FTf' OPT; do\n    case \"$OPT\" in\n      b) OPTS_ENTER=\"$OPTS_ENTER -$OPT\";;\n      c|k|n|t|u)\n         OPTARG=\"$(echo -n \"$OPTARG\" | sed -e \"s/'/'\\\\\\\\\\\\''/g\")\"\n         OPTS_ENTER=\"$OPTS_ENTER -$OPT '$OPTARG'\";;\n      f|F|T) OPTS_XIWI=\"$OPTS_XIWI -$OPT\";;\n      \\?) echo \"$USAGE\" 1>&2\n          exit 2;;\n    esac\ndone\nshift \"$((OPTIND-1))\"\n\nif [ \"$#\" = \"0\" ]; then \n    echo \"$USAGE\" 1>&2\n    exit 2\nfi\n\neval \"exec sh -e \\\"\\$ENTERCHROOT\\\" -t xiwi $OPTS_ENTER \\\n    exec xiwi $OPTS_XIWI \\\"\\$@\\\"\"\n    \n"
  },
  {
    "path": "host-bin/unmount-chroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nALLCHROOTS=''\nBINDIR=\"`dirname \"\\`readlink -f -- \"$0\"\\`\"`\"\nCHROOTS=\"`readlink -m -- \"$BINDIR/../chroots\"`\"\nEXCLUDEROOT=''\nFORCE=''\nPRINT=''\nSIGNAL='TERM'\nTRIES=5\nYES=''\nROOT=\"`readlink -m -- '/var/run/crouton'`\"\n\nUSAGE=\"$APPLICATION [options] name [...]\n\nUnmounts one or more chroots, optionally killing any processes still running\ninside them.\n\nBy default, it will run in interactive mode where it will ask to kill any\nremaining processes if unable to unmount the chroot within 5 seconds.\n\nOptions:\n    -a          Unmount all chroots in the CHROOTS directory.\n    -c CHROOTS  Directory the chroots are in. Default: $CHROOTS\n    -f          Forces a chroot to unmount, potentially breaking or killing\n                other instances of the same chroot.\n    -k KILL     Send the processes SIGKILL instead of SIGTERM.\n    -p          Print to STDOUT the processes stopping a chroot from unmounting.\n    -t TRIES    Number of seconds to try before signalling the processes.\n                Use -t inf to be exceedingly patient. Default: $TRIES\n    -x          Keep the root directory of the chroot mounted.\n    -y          Signal any remaining processes without confirmation.\n                Automatically escalates from SIGTERM to SIGKILL.\"\n\n# Common functions\n. \"$BINDIR/../installer/functions\"\n\n# Process arguments\ngetopts_string='ac:fkpt:xy'\nwhile getopts_nextarg; do\n    case \"$getopts_var\" in\n    a) ALLCHROOTS='y';;\n    c) CHROOTS=\"`readlink -m -- \"$getopts_arg\"`\";;\n    f) FORCE='y';;\n    k) SIGNAL=\"KILL\";;\n    p) PRINT='y';;\n    t) TRIES=\"$getopts_arg\";;\n    x) EXCLUDEROOT='y';;\n    y) YES='a';;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\n\n# Need at least one chroot listed, or -a; not both.\nif [ $# = 0 -a -z \"$ALLCHROOTS\" ] || [ $# != 0 -a -n \"$ALLCHROOTS\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Make sure TRIES is valid\nif [ \"$TRIES\" = inf ]; then\n    TRIES=-1\nelif [ \"$TRIES\" -lt -1 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# We need to run as root\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    error 2 \"$APPLICATION must be run as root.\"\nfi\n\n# Check if a chroot is running with this directory. We detect the\n# appropriate commands by checking if the command's parent root is not equal\n# to the pid's root. This avoids not unmounting due to a lazy-quitting\n# background application within the chroot. We also don't consider processes\n# that have a parent PID of 1 or that of session_manager's (which would mean an\n# orphaned process in this case), as enter-chroot never orphans its children.\n# $1: $base; the canonicalized base path of the chroot\n# Returns: non-zero if the chroot is in use.\ncheckusage() {\n    if [ -n \"$FORCE\" ]; then\n        return 0\n    fi\n    local b=\"${1%/}/\" pid ppid proot prootdir root rootdir pids=''\n    local smgrpid=\"`pgrep -o -u 0 -x session_manager || echo 1`\"\n    for root in /proc/*/root; do\n        if [ ! -r \"$root\" ]; then\n            continue\n        fi\n        rootdir=\"`readlink -f -- \"$root\"`\"\n        rootdir=\"${rootdir%/}/\"\n        if [ \"${rootdir#\"$b\"}\" = \"$rootdir\" ]; then\n            continue\n        fi\n        pid=\"${root#/proc/}\"\n        pid=\"${pid%/root}\"\n        ppid=\"`ps -p \"$pid\" -o ppid= 2>/dev/null | sed 's/ //g'`\"\n        if [ -z \"$ppid\" ] || [ \"$ppid\" -eq 1 -o \"$ppid\" -eq \"$smgrpid\" ]; then\n            continue\n        fi\n        proot=\"/proc/$ppid/root\"\n        if [ -r \"$proot\" ]; then\n            prootdir=\"`readlink -f -- \"$proot\"`\"\n            if [ \"${prootdir%/}/\" = \"$rootdir\" ]; then\n                continue\n            fi\n        fi\n        if [ -n \"$PRINT\" ]; then\n            pids=\"$pids $pid\"\n            continue\n        fi\n        return 1\n    done\n    if [ -n \"$PRINT\" -a -n \"$pids\" ]; then\n        ps -p \"${pids# }\" -o pid= -o cmd= || true\n        return 1\n    fi\n    return 0\n}\n\n# If we specified all chroots, bring in all chroots.\nif [ -n \"$ALLCHROOTS\" ]; then\n    if [ ! -d \"$CHROOTS\" ]; then\n        error 1 \"$CHROOTS not found.\"\n    fi\n    set -- \"$CHROOTS/\"*\nfi\n\n# Follows and fixes dangerous symlinks, returning the canonicalized path.\nfixabslinks() {\n    local p=\"$CHROOT/$1\" c\n    # Follow and fix dangerous absolute symlinks\n    while c=\"`readlink -m -- \"$p\"`\" && [ \"$c\" != \"$p\" ]; do\n        p=\"$CHROOT${c#\"$CHROOT\"}\"\n    done\n    echo \"$p\"\n}\n\n# Unmount the specified chroot $1\n# sets oldstyle if the chroot was unmounted in an old location.\n# if oldstyle is set upon entry, skips the check for old-style mounts.\nunmount() {\n    NAME=\"${1#\"$CHROOTS/\"}\"\n\n    # Check for existence\n    CHROOT=\"$CHROOTS/$NAME\"\n    if [ ! -d \"$CHROOT\" ]; then\n        if [ -z \"$ALLCHROOTS\" ]; then\n            echo \"$CHROOT not found.\" 1>&2\n            ret=1\n        fi\n        return 0\n    fi\n\n    # Switch to the true mount point, but sort of support old-style mounted\n    # chroots with minimal false-positives to ease transition. Don't unmount\n    # old-style twice in a row, though.\n    CHROOTSRC=\"$CHROOT\"\n    oldencr=\"$CHROOTS/.secure/$NAME\"\n    if mountpoint -q \"$oldencr\" \\\n            && [ -d \"$oldencr/etc/crouton\" -a -z \"$oldstyle\" ]; then\n        # Old encrypted chroots\n        oldstyle='y'\n        CHROOT=\"$oldencr\"\n        echo \"$CHROOTSRC appears to be mounted in $CHROOT\" 1>&2\n    elif mountpoint -q \"$CHROOT\" \\\n            && [ -d \"$CHROOT/etc/crouton\" -a -z \"$oldstyle\" ]; then\n        # Keep the chroot the same\n        oldstyle='y'\n        echo \"$CHROOTSRC appears to be mounted in place\" 1>&2\n    else\n        oldstyle=''\n        CHROOT=\"$ROOT/${CHROOT#/}\"\n        if [ ! -d \"$CHROOT\" ]; then\n            # Not mounted\n            return 0\n        fi\n    fi\n\n    base=\"`readlink -f -- \"$CHROOT\"`\"\n\n    if ! checkusage \"$base\"; then\n        echo \"Not unmounting $CHROOTSRC as another instance is using it.\" 1>&2\n        ret=1\n        return 0\n    fi\n\n    # Kill the chroot's system dbus if one is running; failure is fine\n    env -i chroot \"$CHROOT\" su -s '/bin/sh' -c '\n        pidfile=\"/var/run/dbus/pid\"\n        if [ ! -f \"$pidfile\" ]; then\n            exit 0\n        fi\n        pid=\"`cat \"$pidfile\"`\"\n        if ! grep -q \"^dbus-daemon\" \"/proc/$pid/cmdline\" 2>/dev/null; then\n            exit 0\n        fi\n        kill $pid' - root 2>/dev/null || true\n\n    # Unmount all mounts\n    ntries=0\n    if [ -z \"$EXCLUDEROOT\" ]; then\n        echo \"Unmounting $CHROOTSRC...\" 1>&2\n    else\n        echo \"Pruning $CHROOTSRC mounts...\" 1>&2\n    fi\n    baseesc=\"`echo \"$base\" | sed 's= =//=g'`\"\n\n    # Define the mountpoint filter to only unmount specific mounts.\n    # The filter is run on the escaped version of the mountpoint.\n    filter() {\n        if [ -z \"$EXCLUDEROOT\" ]; then\n            grep \"^$baseesc\\\\(/.*\\\\)\\\\?\\$\"\n        else\n            # Don't include the base directory\n            grep \"^$baseesc/.\"\n        fi\n    }\n\n    # Sync for safety\n    sync\n\n    # Make sure the chroot's system media bind-mount is marked as slave to avoid\n    # unmounting devices system-wide. We still want to unmount locally-mounted\n    # media, though.\n    media=\"`fixabslinks '/var/host/media'`\"\n    if mountpoint -q \"$media\"; then\n        mount --make-rslave \"$media\"\n    fi\n\n    # Some /proc/mounts entries may end with \\040(deleted), in that case, try to\n    # umount them with and without the suffix (in the unlikely case the mount\n    # point actually ends with ' (deleted)')\n    # umount has a bug and may return 0 when many mount points cannot be\n    # unmounted, so we call it once per mount point ('-n 1')\n    while ! sed \"s=\\\\\\\\040=//=g\" /proc/mounts | cut -d' ' -f2 | filter \\\n              | sed -e 's=//= =g;s/^\\(\\(.*\\) (deleted)\\)$/\\1\\n\\2/' \\\n              | sort -r | xargs --no-run-if-empty -d '\n' -n 1 umount 2>/dev/null; do\n        if [ \"$ntries\" -eq \"$TRIES\" ]; then\n            # Send signal to all processes running under the chroot\n            # ...but confirm first.\n            printonly=''\n            if [ \"${YES#[Aa]}\" = \"$YES\" ]; then\n                echo -n \"Failed to unmount $CHROOTSRC. Kill processes? [a/k/y/p/N] \" 1>&2\n                if [ -n \"$CROUTON_UNMOUNT_RESPONSE\" ]; then\n                    YES=\"$CROUTON_UNMOUNT_RESPONSE\"\n                    echo \"$YES\" 1>&2\n                else\n                    read -r YES\n                fi\n                if [ \"${YES#[Kk]}\" != \"$YES\" ]; then\n                    SIGNAL='KILL'\n                elif [ \"${YES#[Pp]}\" != \"$YES\" ]; then\n                    printonly=y\n                elif [ \"${YES#[AaYy]}\" = \"$YES\" ]; then\n                    echo \"Skipping unmounting of $CHROOTSRC\" 1>&2\n                    ret=1\n                    break\n                fi\n            fi\n            if [ -z \"$printonly\" ]; then\n                echo \"Sending SIG$SIGNAL to processes under $CHROOTSRC...\" 1>&2\n            fi\n            for root in /proc/*/root; do\n                if [ ! -r \"$root\" ] \\\n                        || [ ! \"`readlink -f -- \"$root\"`\" = \"$base\" ]; then\n                    continue\n                fi\n                pid=\"${root#/proc/}\"\n                pid=\"${pid%/root}\"\n                if [ -n \"${printonly:-\"$PRINT\"}\" ]; then\n                    ps -p \"$pid\" -o pid= -o cmd= || true\n                fi\n                if [ -z \"$printonly\" ]; then\n                    kill \"-$SIGNAL\" \"$pid\" 2>/dev/null || true\n                fi\n            done\n\n            # Escalate\n            if [ \"${YES#[Aa]}\" != \"$YES\" ]; then\n                SIGNAL='KILL'\n            fi\n\n            if [ -z \"$printonly\" ]; then\n                ntries=0\n            fi\n        else\n            ntries=\"$((ntries+1))\"\n        fi\n        sleep 1\n        if ! checkusage \"$base\"; then\n            echo \"Aborting unmounting $CHROOTSRC as another instance has begun using it.\" 1>&2\n            ret=1\n            break\n        fi\n    done\n\n    # More sync for more safety\n    sync\n}\n\n# Unmount each chroot\nret=0\nfor NAME in \"$@\"; do\n    if [ -z \"$NAME\" ]; then\n        continue\n    fi\n    oldstyle=''\n    unmount \"$NAME\"\n    # If we unmounted old-style, do it again in case the new-style was also mounted.\n    if [ -n \"$oldstyle\" ]; then\n        unmount \"$NAME\"\n    fi\ndone\n\n# HACK: restart debugd when running tests to avoid namespace issues.\n# This will go away when we start using mount namespaces.\nif [ -n \"$CROUTON_UNMOUNT_RESTART_DEBUGD\" ]; then\n    restart debugd >/dev/null || true\nfi\n\n# Re-disable USB persistence (the Chromium OS default) if we no longer\n# have chroots running with a root in removable media\nif checkusage \"$ROOT/media\"; then\n    for usbp in /sys/bus/usb/devices/*/power/persist; do\n        if [ -e \"$usbp\" ]; then\n            echo 0 > \"$usbp\"\n        fi\n    done\nfi\n\nexit $ret\n"
  },
  {
    "path": "host-ext/.gitignore",
    "content": "crouton.crx\ncrouton.zip\ncrouton.pem\ncrouton/kiwi.pexe\n"
  },
  {
    "path": "host-ext/crouton/background.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<!-- Copyright (c) 2016 The crouton Authors. All rights reserved.\n     Use of this source code is governed by a BSD-style license that can be\n     found in the LICENSE file. -->\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n    <title></title>\n    <script src=\"background.js\" type=\"text/javascript\"></script>\n  </head>\n  <body>\n    <div><textarea id=\"clipboardholder\" rows=\"2\" cols=\"20\" /></div>\n  </body>\n</html>\n"
  },
  {
    "path": "host-ext/crouton/background.js",
    "content": "// Copyright (c) 2016 The crouton Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n'use strict';\n\n/* Constants */\nvar URL = \"ws://localhost:30001/\";\nvar VERSION = 2; /* Note: the extension must always be backward compatible */\nvar MAXLOGGERLEN = 20;\nvar RETRY_TIMEOUT = 5;\nvar UPDATE_CHECK_INTERVAL = 15*60; /* Check for updates every 15' at most */\nvar WINDOW_UPDATE_INTERVAL = 15; /* Update window list every 15\" at most */\n/* String to copy to the clipboard if it should be empty */\nvar DUMMY_EMPTYSTRING = \"%\";\n\nvar LogLevel = Object.freeze({\n    ERROR : \"error\",\n    INFO  : \"info\",\n    DEBUG : \"debug\"\n});\n\n/* Global variables */\nvar clipboardholder_; /* textarea used to hold clipboard content */\nvar timeout_ = null; /* Set if a timeout is active */\nvar websocket_ = null; /* Active connection */\n\n/* State variables */\nvar debug_ = false;\nvar showlog_ = false; /* true if extension log should be shown */\nvar hidpi_ = false; /* true if kiwi windows should be opened in HiDPI mode */\nvar enabled_ = true; /* true if we are trying to connect */\nvar active_ = false; /* true if we are connected to a server */\nvar error_ = false; /* true if there was an error during the last connection */\nvar dummystr_ = false; /* true if the last string we copied was the dummy string */\nvar update_ = false; /* true if an update to the extension is available */\n\nvar lastupdatecheck_ = null;\nvar lastwindowlistupdate_ = null;\n\nvar status_ = \"\";\nvar sversion_ = 0; /* Version of the websocket server */\nvar logger_ = []; /* Array of status messages: [LogLevel, time, message] */\nvar windows_ = []; /* Array of windows. (.display, .name) */\n\nvar kiwi_win_ = {}; /* Map of kiwi windows. Key is display, value is object\n                        (.id, .isTab, .window: window element) */\nvar focus_win_ = -1; /* Focused kiwi window. -1 if no kiwi window focused. */\n\nvar notifications_ = {}; /* Map of notification id to function to be called when\n                            the notification is clicked. */\n\n/* Check local storage for stored options */\nchrome.storage.local.get(null, function(items){\n    if (typeof items.enabled == \"boolean\") enabled_ = items.enabled;\n    if (typeof items.hidpi == \"boolean\") hidpi_ = items.hidpi;\n    refreshUI();\n});\n\n/* Set the current status string.\n * active is a boolean, true if the WebSocket connection is established. */\nfunction setStatus(status, active) {\n    active_ = active;\n    status_ = status;\n\n    /* Apply update if the extension is not active */\n    if (update_ && !active_)\n        chrome.runtime.reload();\n\n    refreshUI();\n}\n\nfunction showHelp() {\n    window.open(\"first.html\", '_blank');\n}\n\nfunction updateAvailable(version) {\n    printLog(\"A new version of the extension is available (\" + version + \")\",\n             LogLevel.INFO);\n\n    /* Apply update immediately if the extension is not active */\n    if (!active_)\n        chrome.runtime.reload();\n    else\n        update_ = true;\n}\n\nfunction checkUpdate(force) {\n    var currenttime = new Date().getTime();\n\n    if (force || lastupdatecheck_ == null ||\n            (currenttime-lastupdatecheck_) > 1000*UPDATE_CHECK_INTERVAL) {\n        chrome.runtime.requestUpdateCheck(function (status, details) {\n            printLog(\"Update status=\" + status, LogLevel.DEBUG);\n            if (status == \"update_available\") {\n                updateAvailable(details.version);\n            }\n        });\n        lastupdatecheck_ = currenttime;\n    }\n}\n\nfunction updateWindowList(force) {\n    if (!active_ || sversion_ < 2) {\n        windows_ = [];\n        return;\n    }\n\n    var currenttime = new Date().getTime();\n\n    if (force || lastwindowlistupdate_ == null ||\n            (currenttime-lastwindowlistupdate_) > 1000*WINDOW_UPDATE_INTERVAL) {\n        lastwindowlistupdate_ = currenttime;\n        printLog(\"Sending window list request\", LogLevel.DEBUG);\n        websocket_.send(\"Cs\" + focus_win_);\n        websocket_.send(\"Cl\");\n    }\n}\n\n/* Called from kiwi (window.js), so we can directly access each window */\nfunction registerKiwi(displaynum, window) {\n    var display = \":\" + displaynum;\n    if (kiwi_win_[display] && kiwi_win_[display].id >= -1) {\n        kiwi_win_[display].window = window;\n    }\n}\n\n/* Close the popup window */\nfunction closePopup() {\n    var views = chrome.extension.getViews({type: \"popup\"});\n    for (var i = 0; i < views.length; views++) {\n        views[i].close();\n    }\n}\n\n/* Update the icon, and refresh the popup page */\nfunction refreshUI() {\n    updateWindowList(false);\n\n    var icon = \"disconnected\";\n    if (error_)\n        icon = \"error\";\n    else if (!enabled_)\n        icon = \"disabled\";\n    else if (active_)\n        icon = \"connected\";\n\n    chrome.browserAction.setIcon(\n        {path: {19: icon + '-19.png', 38: icon + '-38.png'}}\n    );\n    chrome.browserAction.setTitle({title: 'crouton: ' + icon});\n\n    chrome.browserAction.setBadgeText(\n        {text: windows_.length > 1 ? '' + (windows_.length-1) : ''}\n    );\n    chrome.browserAction.setBadgeBackgroundColor({color: '#2E822B'});\n\n    var views = chrome.extension.getViews({type: \"popup\"});\n    for (var i = 0; i < views.length; views++) {\n        var view = views[i];\n        /* Make sure page is ready */\n        if (view.document.readyState != \"loading\") {\n            /* Update \"help\" link */\n            var helplink = view.document.getElementById(\"help\");\n            helplink.onclick = showHelp;\n            /* Update enable/disable link. */\n            var enablelink = view.document.getElementById(\"enable\");\n            if (enabled_) {\n                enablelink.textContent = \"Disable\";\n                enablelink.onclick = function() {\n                    console.log(\"Disable click\");\n                    enabled_ = false;\n                    /* Update local storage to persist enabled_ boolean */\n                    chrome.storage.local.set({enabled: enabled_});\n                    if (websocket_ != null)\n                        websocket_.close();\n                    else\n                        websocketConnect(); /* Clear timeout and display message */\n                    refreshUI();\n                }\n            } else {\n                enablelink.textContent = \"Enable\";\n                enablelink.onclick = function() {\n                    console.log(\"Enable click\");\n                    enabled_ = true;\n                    /* Update local storage to persist enabled_ boolean */\n                    chrome.storage.local.set({enabled: enabled_});\n                    if (websocket_ == null)\n                        websocketConnect();\n                    refreshUI();\n                }\n            }\n\n            /* Update debug mode according to checkbox state. */\n            var debugcheck = view.document.getElementById(\"debugcheck\");\n            debugcheck.onclick = function() {\n                debug_ = debugcheck.checked;\n                refreshUI();\n                var disps = Object.keys(kiwi_win_);\n                for (var i = 0; i < disps.length; i++) {\n                    var win = kiwi_win_[disps[i]];\n                    if (win.window) {\n                        if (win.isTab) {\n                            chrome.tabs.sendMessage(win.id,\n                                    {func: 'setDebug', param: debug_?1:0});\n                        } else {\n                            win.window.setDebug(debug_?1:0);\n                        }\n                    }\n                }\n            }\n            debugcheck.checked = debug_;\n\n            /* Update hidpi mode according to checkbox state. */\n            var hidpicheck = view.document.getElementById(\"hidpicheck\");\n            if (window.devicePixelRatio > 1) {\n                hidpicheck.onclick = function() {\n                    hidpi_ = hidpicheck.checked;\n                    /* Update local storage to persist hidpi_ setting */\n                    chrome.storage.local.set({hidpi: hidpi_});\n                    refreshUI();\n                    var disps = Object.keys(kiwi_win_);\n                    for (var i = 0; i < disps.length; i++) {\n                        var win = kiwi_win_[disps[i]];\n                        if (win.window) {\n                            if (win.isTab) {\n                                chrome.tabs.sendMessage(win.id,\n                                        {func: 'setHiDPI', param: hidpi_?1:0});\n                            } else {\n                                win.window.setHiDPI(hidpi_?1:0);\n                            }\n                        }\n                    }\n                }\n                hidpicheck.disabled = false;\n            } else {\n                hidpicheck.disabled = true;\n            }\n            hidpicheck.checked = hidpi_;\n\n            /* Update status box */\n            view.document.getElementById(\"info\").textContent = status_;\n\n            /* Update window table */\n            /* FIXME: Improve UI */\n            var windowlist = view.document.getElementById(\"windowlist\");\n\n            while (windowlist.rows.length > 0) {\n                windowlist.deleteRow(0);\n            }\n\n            for (var i = 0; i < windows_.length; i++) {\n                var row = windowlist.insertRow(-1);\n                var cell1 = row.insertCell(0);\n                var cell2 = row.insertCell(1);\n                cell1.className = \"display\";\n                cell1.innerHTML = windows_[i].display;\n                cell2.className = \"name\";\n                cell2.innerHTML = windows_[i].name;\n                cell2.onclick = (function(i) { return function() {\n                    if (active_) {\n                        websocket_.send(\"C\" + windows_[i].display);\n                        closePopup();\n                    }\n                } })(i);\n            }\n\n            /* Update logger table */\n            var loggertable = view.document.getElementById(\"logger\");\n\n            /* FIXME: only update needed rows */\n            while (loggertable.rows.length > 0) {\n                loggertable.deleteRow(0);\n            }\n\n            /* Only update if \"show log\" is enabled */\n            var logcheck = view.document.getElementById(\"logcheck\");\n            logcheck.onclick = function() {\n                showlog_ = logcheck.checked;\n                refreshUI();\n            }\n            logcheck.checked = showlog_;\n            if (showlog_) {\n                for (var i = 0; i < logger_.length; i++) {\n                    var value = logger_[i];\n\n                    if (value[0] == LogLevel.DEBUG && !debug_)\n                        continue;\n\n                    var row = loggertable.insertRow(-1);\n                    var cell1 = row.insertCell(0);\n                    var cell2 = row.insertCell(1);\n                    var levelclass = value[0];\n                    cell1.className = \"time \" + levelclass;\n                    cell2.className = \"value \" + levelclass;\n                    cell1.innerHTML = value[1];\n                    cell2.innerHTML = value[2];\n                }\n            }\n        }\n    }\n}\n\n/* Start the extension */\nfunction clipboardStart() {\n    printLog(\"Extension started (\" + chrome.runtime.getManifest().version + \")\",\n             LogLevel.INFO);\n    setStatus(\"Started...\", false);\n\n    /* Monitor window/tab focus changes/removals and report to croutonclip */\n    chrome.windows.onFocusChanged.addListener(\n            function(id) { onFocusChanged(id, false); });\n    chrome.windows.onRemoved.addListener(\n            function(id) { onRemoved(id, false); });\n    chrome.tabs.onActivated.addListener(\n            function(data) { onFocusChanged(data.tabId, true); });\n    chrome.tabs.onRemoved.addListener(\n            function(id, data) { onRemoved(id, true); });\n\n    clipboardholder_ = document.getElementById(\"clipboardholder\");\n\n    /* Notification event handlers */\n    chrome.notifications.onClosed.addListener(notificationClosed);\n    chrome.notifications.onClicked.addListener(notificationClicked);\n\n    websocketConnect();\n}\n\n/* Connect to the server */\nfunction websocketConnect() {\n    /* Clear timeout if we were called manually. */\n    if (timeout_ != null) {\n        clearTimeout(timeout_);\n        timeout_ = null;\n    }\n\n    if (!enabled_) {\n        setStatus(\"No connection (extension disabled)\", false);\n        printLog(\"Extension is disabled\", LogLevel.INFO);\n        return;\n    }\n\n    if (websocket_ != null) {\n        printLog(\"Socket already open\", LogLevel.DEBUG);\n        return;\n    }\n\n    console.log(\"websocketConnect: \" + websocket_);\n\n    printLog(\"Opening a web socket\", LogLevel.DEBUG);\n    error_ = false;\n    setStatus(\"Connecting...\", false);\n    websocket_ = new WebSocket(URL);\n    websocket_.onopen = websocketOpen;\n    websocket_.onmessage = websocketMessage;\n    websocket_.onclose = websocketClose;\n}\n\n/* Connection was established */\nfunction websocketOpen() {\n    printLog(\"Connection established\", LogLevel.INFO);\n    setStatus(\"Connected: checking version...\", false);\n}\n\nfunction readClipboard() {\n    clipboardholder_.value = \"\";\n    clipboardholder_.select();\n    document.execCommand(\"Paste\");\n    return clipboardholder_.value;\n}\n\nfunction writeClipboard(str) {\n    clipboardholder_.value = str;\n    clipboardholder_.select();\n    document.execCommand(\"Copy\");\n}\n\n/* Received a message from the server */\nfunction websocketMessage(evt) {\n    var received_msg = evt.data;\n    var cmd = received_msg[0];\n    var payload = received_msg.substring(1);\n\n    printLog(\"Message received (\" + received_msg + \")\", LogLevel.DEBUG);\n\n    /* Only accept version packets until we have received one. */\n    if (!active_) {\n        if (cmd == 'V') { /* Version */\n            sversion_ = payload;\n            if (sversion_ < 1 || sversion_ > VERSION) {\n                websocket_.send(\"EInvalid version (> \" + VERSION + \")\");\n                error(\"Invalid server version \" + sversion_ + \" > \" + VERSION,\n                      false);\n            }\n            websocket_.send(\"VOK\");\n            /* Set active_ to true */\n            setStatus(sversion_ >= 2 ? \"\" : \"Connected\", true);\n            /* Force a window list update */\n            updateWindowList(true);\n            return;\n        } else {\n            error(\"Received frame while waiting for version\", false);\n        }\n    }\n\n    switch(cmd) {\n    case 'W': /* Write */\n        var clip = readClipboard();\n\n        dummystr_ = false;\n\n        /* Do not erase identical clipboard content */\n        if (clip != payload) {\n             /* We cannot write an empty string: Write DUMMY instead */\n            if (payload == \"\") {\n                writeClipboard(DUMMY_EMPTYSTRING);\n                dummystr_ = true;\n            } else {\n                writeClipboard(payload);\n            }\n        } else if (payload == DUMMY_EMPTYSTRING) {\n            /* Unlikely case where DUMMY string comes from the other side */\n            writeClipboard(payload);\n        } else {\n            printLog(\"Not erasing content (identical)\", LogLevel.DEBUG);\n        }\n\n        websocket_.send(\"WOK\");\n\n        break;\n    case 'R': /* Read */\n        var clip = readClipboard();\n\n        if (clip == DUMMY_EMPTYSTRING && dummystr_) {\n            websocket_.send(\"R\");\n        } else {\n            websocket_.send(\"R\" + clip);\n        }\n\n        break;\n    case 'U': /* Open an URL */\n        /* URL must be absolute: see RFC 3986 for syntax (section 3.1) */\n        if (match = (/^([a-z][a-z0-9+-.]*):/i).exec(payload)) {\n            /* FIXME: we could blacklist schemes using match[1] here */\n            chrome.tabs.create({url: payload});\n            websocket_.send(\"UOK\");\n        } else {\n            printLog(\"Received invalid URL: \" + payload, LogLevel.ERROR);\n            websocket_.send(\"EError: URL must be absolute\");\n        }\n\n        break;\n    case 'N': /* Raise a notification */\n        /* Payload in JSON format, compatible with chrome.extensions specifications */\n        try {\n            var data = JSON.parse(payload);\n\n            if (!data.type)\n                data.type = \"basic\";\n            if (!data.iconUrl)\n                data.iconUrl = \"icon-128.png\";\n\n            /* Strip off crouton fields */\n            var id = \"\";\n            var display = null;\n\n            if (data.crouton_id) {\n                id = data.crouton_id;\n            }\n            delete data.crouton_id;\n\n            /* Set context message with chroot name/display */\n            delete data.contextMessage;\n            if (data.crouton_display) {\n                display = data.crouton_display;\n                var win = windows_.filter(function(x) {\n                                              return x.display == display })[0];\n                var name = win ? (win.name + \" (\" + display + \")\") : display;\n                data.contextMessage = \"Switch to \" + name;\n            }\n            delete data.crouton_display;\n\n            chrome.notifications.create(id, data,\n                function(id) {\n                    printLog(\"Raised notification \" + id, LogLevel.DEBUG);\n                    notifications_[id] = function() {\n                        if (display)\n                            websocket_.send(\"C\" + display);\n                        /* Remove the notification. */\n                        chrome.notifications.clear(id, function(_) {});\n                    }\n                });\n            websocket_.send(\"NOK\");\n        } catch(e) {\n            printLog(\"Notification parsing error: \" + e +\n                     \" (payload: '\" + payload + \"').\", LogLevel.ERROR);\n            websocket_.send(\"EError: invalid payload.\");\n        }\n        break;\n    case 'C': /* Returned data from a croutoncycle command */\n        /* Non-zero length has a window list; otherwise it's a cycle signal */\n        if (payload.length > 0) {\n            windows_ = payload.split('\\n').map(\n                function(x) {\n                    var m = x.match(/^([^ *]*)\\*? +(.*)$/);\n                    if (!m)\n                        return null;\n\n                    /* Only display cros and X11 servers (no window) */\n                    if (m[1] != \"cros\" && !m[1].match(/^:([0-9]+)$/))\n                        return null;\n\n                    var k = new Object();\n                    k.display = m[1];\n                    k.name = m[2];\n                    return k;\n                }\n            ).filter( function(x) { return !!x; } );\n\n            windows_.forEach(function(k) {\n                var win = kiwi_win_[k.display];\n                if (win && win.window) {\n                    if (win.isTab) {\n                        chrome.tabs.sendMessage(win.id,\n                                {func: 'setTitle', param: k.name});\n                    } else {\n                        win.window.setTitle(k.name);\n                    }\n                }\n            });\n\n            lastwindowlistupdate_ = new Date().getTime();\n            websocket_.send(\"COK\");\n        }\n        refreshUI();\n        break;\n    case 'X': /* Ask to open a crouton window */\n        var display = payload;\n        var match = display.match(/^:([0-9]+)([- ][^- ]*)*$/);\n        var displaynum = match ? match[1] : null;\n        var mode = null;\n        if (displaynum) {\n            display = \":\" + displaynum;\n            mode = match[2] && match[2].length >= 2 ? match[2].charAt(1) : 'f';\n            if ('fwt'.indexOf(mode) == -1) {\n                console.log('invalid xiwi mode: ' + mode);\n                mode = 'f';\n            }\n        }\n        if (!displaynum) {\n            /* Minimize all kiwi windows  */\n            var disps = Object.keys(kiwi_win_);\n            for (var i = 0; i < disps.length; i++) {\n                if (kiwi_win_[disps[i]].isTab) {\n                    continue;\n                }\n                var winid = kiwi_win_[disps[i]].id;\n                chrome.windows.update(winid, {focused: false});\n\n                var minimize = function(win) {\n                    chrome.windows.update(winid, {state: 'minimized'}); };\n\n                chrome.windows.get(winid, function(win) {\n                    /* To make restore nicer, first exit full screen,\n                     * then minimize */\n                    if (win.state == \"fullscreen\") {\n                        chrome.windows.update(winid, {state: 'maximized'},\n                                              minimize);\n                    } else {\n                        minimize();\n                    }\n                });\n            }\n        } else if (kiwi_win_[display] && kiwi_win_[display].id >= 0 &&\n                   (!kiwi_win_[display].window ||\n                    !kiwi_win_[display].window.closing)) {\n            /* focus/full screen an existing window */\n            var winid = kiwi_win_[display].id;\n            if (kiwi_win_[display].isTab) {\n                chrome.tabs.update(winid, {active: true});\n                chrome.tabs.get(winid, function(tab) {\n                    chrome.windows.update(tab.windowId, {focused: true});\n                });\n            } else {\n                chrome.windows.update(winid, {focused: true});\n                chrome.windows.get(winid, function(win) {\n                    if (win.state == \"maximized\")\n                        chrome.windows.update(winid, {state: 'fullscreen'});\n                });\n            }\n        } else {\n            /* Open a new window */\n            kiwi_win_[display] = new Object();\n            kiwi_win_[display].id = -1;\n            kiwi_win_[display].isTab = (mode == 't');\n            kiwi_win_[display].window = null;\n\n            var win = windows_.filter(function(x){return x.display == display})[0];\n            var name = win ? win.name : \"crouton in a window\";\n            var create = chrome.windows.create;\n            var data = {};\n\n            if (kiwi_win_[display].isTab) {\n                name = win ? win.name : \"crouton in a tab\";\n                create = chrome.tabs.create;\n            } else {\n                data['type'] = \"popup\";\n            }\n\n            data['url'] = \"window.html?display=\" + displaynum +\n                          \"&debug=\" + (debug_ ? 1 : 0) +\n                          \"&hidpi=\" + (hidpi_ ? 1 : 0) +\n                          \"&title=\" + encodeURIComponent(name) +\n                          \"&mode=\" + mode;\n\n            create(data, function(newwin) {\n                             kiwi_win_[display].id = newwin.id;\n                             focus_win_ = display;\n                             if (active_ && sversion_ >= 2)\n                                 websocket_.send(\"Cs\" + focus_win_);\n                         });\n        }\n        websocket_.send(\"XOK\");\n        closePopup();\n        /* Force a window list update */\n        updateWindowList(true);\n        break;\n    case 'P': /* Ping */\n        websocket_.send(received_msg);\n        break;\n    case 'E':\n        error(\"Server error: \" + payload, 1);\n        break;\n    default:\n        error(\"Invalid packet from server: \" + received_msg, 1);\n        break;\n    }\n}\n\n/* Connection was closed (or never established) */\nfunction websocketClose() {\n    if (websocket_ == null) {\n        console.log(\"websocketClose: null!\");\n        return;\n    }\n\n    printLog(\"Connection closed\", active_ ? LogLevel.INFO : LogLevel.DEBUG);\n    if (enabled_) {\n        setStatus(\"Disconnected (retrying every \" + RETRY_TIMEOUT + \" seconds)\",\n                  false);\n        /* Retry in RETRY_TIMEOUT seconds */\n        if (timeout_ == null) {\n            timeout_ = setTimeout(websocketConnect, RETRY_TIMEOUT*1000);\n        }\n    } else {\n        setStatus(\"Disconnected (extension disabled)\", false);\n    }\n\n    websocket_ = null;\n\n    /* Check for update on every disconnect */\n    checkUpdate(false);\n}\n\n/* Called when window/tab in focus changes: feedback to the extension so the\n * clipboard can be transfered. */\nfunction onFocusChanged(id, isTab) {\n    var disps = Object.keys(kiwi_win_);\n    var nextfocus_win = \"cros\";\n    for (var i = 0; i < disps.length; i++) {\n        if (kiwi_win_[disps[i]].isTab == isTab\n                && kiwi_win_[disps[i]].id == id) {\n            nextfocus_win = disps[i];\n            break;\n        }\n    }\n    if (focus_win_ != nextfocus_win) {\n        focus_win_ = nextfocus_win;\n        if (active_ && sversion_ >= 2)\n            websocket_.send(\"Cs\" + focus_win_);\n        printLog(\"Window \" + focus_win_ + \" focused\", LogLevel.DEBUG);\n    }\n}\n\n/* Called when a window/tab is removed, so we can delete its reference. */\nfunction onRemoved(id, isTab) {\n    var disps = Object.keys(kiwi_win_);\n    for (var i = 0; i < disps.length; i++) {\n        if (kiwi_win_[disps[i]].isTab == isTab\n                && kiwi_win_[disps[i]].id == id) {\n            kiwi_win_[disps[i]].id = -2;\n            kiwi_win_[disps[i]].isTab = false;\n            kiwi_win_[disps[i]].window = null;\n            printLog(\"Window \" + disps[i] + \" removed\", LogLevel.DEBUG);\n            /* Force a window list update */\n            updateWindowList(true);\n        }\n    }\n}\n\n/* Called when a notification is clicked */\nfunction notificationClicked(id) {\n    printLog(\"Notification \" + id + \" clicked.\", LogLevel.DEBUG);\n    if (notifications_[id]) {\n        notifications_[id]();\n    }\n}\n\n/* Called when a notification is closed */\nfunction notificationClosed(id, byUser) {\n    printLog(\"Notification \" + id + \" closed (byUser: \" + byUser + \").\",\n             LogLevel.DEBUG);\n    delete notifications_[id];\n}\n\nfunction padstr0(i) {\n    var s = i + \"\";\n    if (s.length < 2)\n        return \"0\" + s;\n    else\n        return s;\n}\n\n/* Add a message in the log. */\nfunction printLog(str, level) {\n    var date = new Date;\n    var datestr = padstr0(date.getHours()) + \":\" +\n                  padstr0(date.getMinutes()) + \":\" +\n                  padstr0(date.getSeconds());\n\n    if (str.length > 200)\n        str = str.substring(0, 197) + \"...\";\n    console.log(datestr + \": \" + str);\n\n    /* Add messages to logger */\n    if (level != LogLevel.DEBUG || debug_) {\n        logger_.unshift([level, datestr, str]);\n        if (logger_.length > MAXLOGGERLEN) {\n            logger_.pop();\n        }\n        refreshUI();\n    }\n}\n\n/* Display an error, and prevent retries if enabled is false */\nfunction error(str, enabled) {\n    printLog(str, LogLevel.ERROR);\n    enabled_ = enabled;\n    error_ = true;\n    refreshUI();\n    if (websocket != null)\n        websocket_.close();\n    /* Force check for extension update (possible reason for the error) */\n    checkUpdate(true);\n}\n\n/* Open help tab on first install on Chromium OS */\nchrome.runtime.onInstalled.addListener(function(details) {\n    if (details.reason == \"install\") {\n        chrome.runtime.getPlatformInfo(function(platforminfo) {\n            if (platforminfo.os == 'cros') {\n                showHelp();\n            }\n        });\n    }\n});\n\n/* Initialize, taking into account the platform */\nchrome.runtime.getPlatformInfo(function(platforminfo) {\n    if (platforminfo.os == 'cros') {\n        /* On error: disconnect WebSocket, then log errors */\n        var onerror = function(msg, url, line) {\n            if (websocket_)\n                websocket_.close();\n            error(\"Uncaught JS error: \" + msg, false);\n            return true;\n        }\n\n        /* Start the extension as soon as the background page is loaded */\n        if (document.readyState == 'complete') {\n            clipboardStart();\n        } else {\n            document.addEventListener('DOMContentLoaded', clipboardStart);\n        }\n\n        chrome.runtime.onUpdateAvailable.addListener(function(details) {\n            updateAvailable(details.version);\n        });\n    } else {\n        /* Disable the icon on non-Chromium OS. */\n        chrome.browserAction.setTitle(\n            {title: 'crouton is not available on this platform'}\n        );\n        chrome.browserAction.disable();\n    }\n});\n"
  },
  {
    "path": "host-ext/crouton/first.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<!-- Copyright (c) 2016 The crouton Authors. All rights reserved.\n     Use of this source code is governed by a BSD-style license that can be\n     found in the LICENSE file. -->\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n    <title>crouton integration extension</title>\n    <style type=\"text/css\">\n      div.text {\n        margin-bottom: 1em;\n        font-size: 16pt;\n      }\n\n      h1 {\n        font-size: 40pt;\n      }\n    </style>\n  </head>\n  <body>\n    <div style=\"width: 800px; margin:0 auto\">\n    <img style=\"vertical-align: top; float:left\" src=\"icon-128.png\" alt=\"crouton\" title=\"much integrate...wow\"/>\n    <div style=\"text-align: center; width: 650px; float:left\">\n    <h1 style=\"display:inline; line-height: 64px\">crouton integration<br/>extension</h1>\n    </div>\n    <hr style=\"margin-bottom: 1em; clear:both;\"/>\n    <div class=\"text\">Thank you for installing the crouton extension!</div>\n    <div class=\"text\">This extension provides clipboard synchronization, URL handler, and an X11 window to <a href=\"https://github.com/dnschneid/crouton\">crouton</a> chroots.</div>\n    <div class=\"text\">If you have not done so yet, you should download the <a href=\"https://raw.githubusercontent.com/dnschneid/crouton/master/installer/crouton\">crouton installer</a>.</div>\n    <div class=\"text\">Then follow instructions in the <a href=\"https://github.com/dnschneid/crouton/blob/master/README.md\">README</a> to get your first chroot running.</div>\n    <div class=\"text\">Be sure to install the <em>extension</em> or <em>xiwi</em> targets with the chroot.</div>\n    <div class=\"text\">Need more help? Check out the <a href=\"https://github.com/dnschneid/crouton/wiki\">wiki</a>!</div>\n    </div>\n  </body>\n</html>\n\n"
  },
  {
    "path": "host-ext/crouton/kiwi.nmf",
    "content": "{\n  \"program\": {\n    \"portable\": {\n      \"pnacl-translate\": {\n        \"url\": \"kiwi.pexe\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "host-ext/crouton/manifest.json",
    "content": "{\n  \"manifest_version\": 2,\n\n  \"name\": \"crouton integration\",\n  \"short_name\": \"crouton\",\n  \"description\": \"Improves integration with crouton chroots.\",\n  \"version\": \"2.5.2\",\n  \"icons\": {\n    \"48\": \"icon-48.png\",\n    \"128\": \"icon-128.png\"\n  },\n\n  \"offline_enabled\": true,\n  \"browser_action\": {\n    \"default_icon\": {\n        \"19\": \"disconnected-19.png\",\n        \"38\": \"disconnected-38.png\"\n    },\n    \"default_popup\": \"popup.html\",\n    \"default_title\": \"crouton\"\n  },\n  \"background\": {\n    \"page\": \"background.html\"\n  },\n  \"permissions\": [\n    \"clipboardRead\",\n    \"clipboardWrite\",\n    \"notifications\",\n    \"storage\"\n  ]\n}\n"
  },
  {
    "path": "host-ext/crouton/popup.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<!-- Copyright (c) 2016 The crouton Authors. All rights reserved.\n     Use of this source code is governed by a BSD-style license that can be\n     found in the LICENSE file. -->\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n    <title>crouton integration extension popup</title>\n    <style type=\"text/css\">\n      body {\n        min-width: 400px;\n        max-width: 400px;\n        overflow-x: hidden;\n      }\n\n      a { // Do not show focus outline\n        outline: 0;\n      }\n\n      #windowlist {\n        border: 0px;\n        border-spacing: 0;\n      }\n\n      #logger {\n        border: 0px;\n        border-spacing: 0;\n      }\n\n      td {\n        font-family: \"monospace\";\n        font-size: 12px;\n        vertical-align: top;\n      }\n\n      td.display {\n        padding: 2px 5px;\n        font-size: 20px;\n        color:#666666;\n      }\n\n      td.name {\n        padding: 2px 5px;\n        font-size: 20px;\n        color:#000080;\n        cursor:pointer;\n      }\n\n      td.time {\n        padding: 2px 0px;\n        color:#606060;\n      }\n\n      td.time.error {\n        background-color: #FF0000;\n        color: #000000;\n      }\n\n      td.value {\n        padding: 2px 15px;\n      }\n      td.value.debug { color: #808080; }\n      td.value.info { color: #008000; }\n      td.value.error { color: #800000; }\n\n      input:disabled+label { color:#cccccc; }\n    </style>\n    <script src=\"popup.js\" type=\"text/javascript\"></script>\n  </head>\n  <body>\n    <div style=\"float: right;\"><a href=\"#\" id=\"help\">Help?</a>\n      <br/><a href=\"#\" id=\"enable\">.</a></div>\n    <h1 style=\"display:inline\">crouton integration</h1>\n    <hr/>\n    <div id=\"info\">Loading...</div>\n    <div id=\"switcher\"><table id=\"windowlist\"><tr><td /></tr></table></div>\n    <div id=\"scrolllogger\"><table id=\"logger\"><tr><td /></tr></table></div>\n    <div style=\"float:right\">\n      <input type=\"checkbox\" id=\"hidpicheck\" />\n        <label for=\"hidpicheck\">HiDPI</label>\n      <input type=\"checkbox\" id=\"logcheck\" />\n        <label for=\"logcheck\">Show log</label>\n      <input type=\"checkbox\" id=\"debugcheck\" />\n        <label for=\"debugcheck\">Enable debugging</label>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "host-ext/crouton/popup.js",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n'use strict';\n\ndocument.addEventListener('DOMContentLoaded', function() {\n    chrome.extension.getBackgroundPage().refreshUI();\n});\n"
  },
  {
    "path": "host-ext/crouton/window.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n    \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<!-- Copyright (c) 2016 The crouton Authors. All rights reserved.\n     Use of this source code is governed by a BSD-style license that can be\n     found in the LICENSE file. -->\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n    <meta http-equiv=\"Pragma\" content=\"no-cache\" />\n    <meta http-equiv=\"Expires\" content=\"-1\" />\n    <title>Crouton in a tab</title>\n    <script type=\"text/javascript\" src=\"window.js\"></script>\n    <style type=\"text/css\">\n      body {\n        margin: 0;\n        padding: 0;\n        overflow: hidden;\n        background-color: rgba(0, 0, 0, 1);\n      }\n      #content {\n        position: absolute;\n        top: 0;\n        right: 0;\n        bottom: 0;\n        left: 0;\n      }\n      #debug {\n        color: rgba(255, 255, 255, 1);\n      }\n      #header {\n        height: 15px;\n        position: absolute;\n        top: 0;\n        left: 0;\n        right: 0;\n      }\n      #info {\n        position: absolute;\n        width: 100%;\n        margin-top: 100px;\n        font-size: 12pt;\n        text-align: center;\n        color: rgba(255, 255, 255, 1);\n      }\n      #status {\n        background-color: rgba(0, 0, 0, 1);\n      }\n      #warning {\n        background-color: rgba(128, 128, 0, 1);\n      }\n      .close {\n        background-color: rgba(0, 0, 0, 0);\n        color: rgba(255, 255, 0, 1);\n        font-size: 12pt;\n        padding-bottom: 1px;\n        padding-top: 2px;\n        padding-left: 4px;\n        padding-right: 4px;\n        border-width: 0px;\n      }\n      .close:focus {\n        outline: none;\n      }\n      .close:active {\n        background-color: rgba(0, 0, 0, 0.5);\n      }\n      .close:hover {\n        color: rgba(255, 0, 0, 1);\n      }\n      #error {\n        background-color: rgba(128, 0, 0, 1);\n      }\n      #warning, #error {\n        display: none;\n      }\n      #listener {\n        width: 100%;\n        height: 100%;\n      }\n      #kiwi {\n        margin-left: 0px;\n        margin-top: 0px;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"content\">\n      <div id=\"header\">\n        <div id=\"debug\">Initializing...</div>\n      </div>\n      <div id=\"info\">\n        <div id=\"status\">Initializing...</div>\n        <div id=\"warning\">\n          <button class=\"close\" title=\"Dismiss\">&#9746;</button>\n          WARNING: <span class=\"text\"></span>\n        </div>\n        <div id=\"error\">ERROR: <span class=\"text\"></span></div>\n      </div>\n      <div id=\"listener\">\n        <embed id=\"kiwi\"\n               width=\"100%\" height=\"100%\"\n               src=\"kiwi.nmf\"\n               type=\"application/x-pnacl\" />\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "host-ext/crouton/window.js",
    "content": "// Copyright (c) 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n'use strict';\n\nvar CLOSE_TIMEOUT = 0; /* Close window x seconds after disconnect */\nvar DEBUG_LEVEL = 2; /* If debug is enabled, use this level in NaCl */\nvar RESIZE_RATE_LIMIT = 300; /* No more than 1 resize query every x ms */\n\nvar KiwiModule_ = null; /* NaCl module */\nvar listener_ = null; /* listener div element */\nvar infodiv_ = null; /* info div (contains status, warning(s), error(s)) */\nvar statusdiv_ = null; /* status div */\nvar warningdiv_ = null; /* warning div */\nvar errordiv_ = null; /* error div */\n\nvar debug_ = 0; /* Debuging level, passed to NaCl module */\nvar hidpi_ = 0; /* HiDPI mode */\nvar display_ = -1; /* Display number to use */\nvar title_ = \"crouton\"; /* window title */\nvar connected_ = false;\nvar closing_ = false; /* Disconnected, and waiting for the window to close */\nvar error_ = false; /* An error has occured */\n\nvar prevstate_ = \"maximized\"; /* Previous window state (before full screen) */\n\n /* Rate limit resize events */\nvar resizePending_ = false;\nvar resizeLimited_ = false;\n\nfunction registerWindow(register) {\n    chrome.extension.getBackgroundPage().\n        registerKiwi(display_, register ? window : null);\n}\n\n/* NaCl module loaded */\nfunction moduleDidLoad() {\n    KiwiModule_ = document.getElementById('kiwi');\n    setStatus('Starting...');\n    KiwiModule_.postMessage('debug:' + debug_);\n    KiwiModule_.postMessage('hidpi:' + hidpi_);\n    /* Sending the display command triggers a connection: send it last. */\n    KiwiModule_.postMessage('display:' + display_);\n    KiwiModule_.focus();\n}\n\n/* NaCl is loading... */\nfunction handleProgress(event) {\n    /* We could compute a percentage, but loading gets stuck at 89% (while\n     * translating?), so it's not very useful... */\n    setStatus('Loading...');\n}\n\n/* NaCl module failed to load */\nfunction handleError(event) {\n    // We can't use common.naclModule yet because the module has not been\n    // loaded.\n    KiwiModule_ = document.getElementById('kiwi');\n    showError(KiwiModule_.lastError);\n    registerWindow(false);\n}\n\n/* NaCl module crashed */\nfunction handleCrash(event) {\n    if (KiwiModule_.exitStatus == -1) {\n        showError('NaCl module crashed.');\n    } else {\n        showError('NaCl module exited: ' + KiwiModule_.exitStatus);\n    }\n    registerWindow(false);\n}\n\n/* Handle requests from the background page (for tabs) */\nfunction handleRequest(message, sender, sendResponse) {\n    if (typeof(window[message.func]) == \"function\") {\n        window[message.func](message.param);\n    }\n};\n\n/* Change debugging level */\nfunction setDebug(debug) {\n    debug_ = (debug > 0) ? DEBUG_LEVEL : 0;\n    if (debug_ > 0) {\n        document.getElementById('content').style.paddingTop = \"16px\";\n        document.getElementById('header').style.display = 'block';\n    } else {\n        document.getElementById('content').style.paddingTop = \"0px\";\n        document.getElementById('header').style.display = 'none';\n    }\n    if (KiwiModule_) {\n        KiwiModule_.postMessage('debug:' + debug_);\n        kiwiResize();\n    }\n}\n\n/* Change HiDPI mode */\nfunction setHiDPI(hidpi) {\n    hidpi_ = hidpi;\n    if (KiwiModule_) {\n        KiwiModule_.postMessage('hidpi:' + hidpi_);\n        kiwiResize();\n    }\n}\n\nfunction setTitle(title) {\n    document.title = title;\n}\n\n/* Set status message */\nfunction setStatus(message) {\n    if (message) {\n        statusdiv_.textContent = message;\n        statusdiv_.style.display = 'block';\n    } else {\n        statusdiv_.style.display = 'none';\n    }\n}\n\n/* Set warning message */\nfunction showWarning(message) {\n    var div = addInfoLine(warningdiv_, message);\n    var warningclose = div.getElementsByClassName(\"close\")[0];\n    warningclose.onclick = function() { infodiv_.removeChild(div); };\n}\n\n/* Set error message */\nfunction showError(message) {\n    error_ = true;\n    setStatus(null);\n    addInfoLine(errordiv_, message);\n}\n\n/* Adds warning/error line to info div. Returns duplicated element */\nfunction addInfoLine(div, message) {\n    var newdiv = div.cloneNode(true);\n    var divtext = newdiv.getElementsByClassName(\"text\")[0];\n    divtext.textContent = message;\n    /* Insert all warnings/errors before the status line. */\n    infodiv_.insertBefore(newdiv, statusdiv_);\n    return newdiv;\n}\n\n/* This function is called when a message is received from the NaCl module. */\n/* Message format is type:payload */\nfunction handleMessage(message) {\n    var str = message.data;\n    var type, payload, i;\n    if ((i = str.indexOf(\":\")) > 0) {\n        type = str.substr(0, i);\n        payload = str.substr(i+1);\n    } else {\n        type = \"debug\";\n        payload = str;\n    }\n\n    console.log(message.data);\n\n    if (type == \"debug\") {\n        var debugEl = document.getElementById('debug');\n        if (debugEl)\n            debugEl.textContent = message.data;\n    } else if (type == \"status\") {\n        setStatus(payload);\n    } else if (type == \"warning\") {\n        showWarning(payload);\n    } else if (type == \"error\") {\n        showError(payload);\n    } else if (type == \"connected\") {\n        connected_ = true;\n        setStatus(null);\n    } else if (type == \"disconnected\") {\n        connected_ = false;\n        if (debug_ < 1 && !error_) {\n            closing_ = true;\n            setStatus(\"Disconnected, closing window in \" +\n                         CLOSE_TIMEOUT + \" seconds.\");\n            setTimeout(function() { window.close() }, CLOSE_TIMEOUT*1000);\n        } else {\n            setStatus(\"Disconnected, please close the window.\");\n        }\n        registerWindow(false);\n    } else if (type == \"state\" && payload == \"fullscreen\") {\n        /* Toggle full screen */\n        chrome.windows.getCurrent(function(win) {\n            var newstate = prevstate_;\n            if (win.state != \"fullscreen\") {\n                prevstate_ = win.state;\n                newstate = \"fullscreen\";\n            }\n            chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,\n                                  {state: newstate}, function(win) {});\n        });\n    } else if (type == \"state\" && payload == \"hide\") {\n        /* Hide window */\n        chrome.windows.getCurrent(function(win) {\n            var minimize = function(win) {\n                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,\n                                      {state: 'minimized'}, function(win) {})}\n            /* To make restore nicer, first exit full screen, then minimize */\n            if (win.state == \"fullscreen\") {\n                chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,\n                                      {state: 'maximized'}, minimize);\n            } else {\n                minimize();\n            }\n        });\n    } else if (type == \"resize\") {\n        i = payload.indexOf(\"/\");\n        if (i < 0) return;\n        /* FIXME: Show scroll bars if the window is too small */\n        var width = payload.substr(0, i);\n        var height = payload.substr(i+1);\n        var lwidth = listener_.clientWidth;\n        var lheight = listener_.clientHeight;\n        var marginleft = (lwidth-width)/2;\n        var margintop = (lheight-height)/2;\n        KiwiModule_.style.marginLeft = Math.max(marginleft, 0) + \"px\";\n        KiwiModule_.style.marginTop = Math.max(margintop, 0) + \"px\";\n        KiwiModule_.width = width;\n        KiwiModule_.height = height;\n    }\n}\n\n/* Tell the module that the window was resized (this triggers a change of\n * resolution, followed by a resize message. */\nfunction kiwiResize() {\n    console.log(\"resize! \" + listener_.clientWidth + \"/\" + listener_.clientHeight);\n    if (KiwiModule_)\n        KiwiModule_.postMessage('resize:' + listener_.clientWidth + \"/\" + listener_.clientHeight);\n}\n\n/* Window was resize, limit to one event per second */\nfunction handleResize() {\n    if (!resizeLimited_) {\n        kiwiResize();\n        setTimeout(function() {\n            if (resizePending_)\n                kiwiResize();\n            resizeLimited_ = resizePending_ = false;\n        }, RESIZE_RATE_LIMIT);\n        resizeLimited_ = true;\n    } else {\n        resizePending_ = true;\n    }\n}\n\n/* Called when window changes focus/visiblity */\nfunction handleFocusBlur(evt) {\n    /* Unfortunately, hidden/visibilityState is not able to tell when a window\n     * is not visible at all (e.g. in the background).\n     * See http://crbug.com/403061 */\n    console.log(\"focus/blur: \" + evt.type + \", focus=\" + document.hasFocus() +\n                \", hidden=\" + document.hidden + \"/\" + document.visibilityState);\n    if (!KiwiModule_)\n        return;\n\n    if (document.hasFocus()) {\n        KiwiModule_.postMessage(\"focus:\");\n    } else {\n        if (closing_)\n            window.close();\n\n        if (!document.hidden)\n            KiwiModule_.postMessage(\"blur:\");\n        else\n            KiwiModule_.postMessage(\"hide:\");\n    }\n    console.log(\"active: \" + document.activeElement);\n    KiwiModule_.focus();\n}\n\n/* Parse arguments */\nlocation.search.substring(1).split('&').forEach(function(arg) {\n    var keyval = arg.split('=');\n    if (keyval[0] == \"display\") {\n        display_ = keyval[1];\n    } else if (keyval[0] == \"title\") {\n        title_ = decodeURIComponent(keyval[1]);\n    } else if (keyval[0] == \"debug\") {\n        debug_ = keyval[1];\n    } else if (keyval[0] == \"hidpi\") {\n        hidpi_ = keyval[1];\n    } else if (keyval[0] == \"mode\") {\n        if (keyval[1] == 'f') {\n            chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT,\n                                  {state: \"fullscreen\"});\n        }\n    }\n});\n\ndocument.addEventListener('DOMContentLoaded', function() {\n    listener_ = document.getElementById('listener');\n    listener_.addEventListener('load', moduleDidLoad, true);\n    listener_.addEventListener('progress', handleProgress, true);\n    listener_.addEventListener('error', handleError, true);\n    listener_.addEventListener('crash', handleCrash, true);\n    listener_.addEventListener('message', handleMessage, true);\n    window.addEventListener('resize', handleResize);\n    window.addEventListener('focus', handleFocusBlur);\n    window.addEventListener('blur', handleFocusBlur);\n    document.addEventListener('visibilitychange', handleFocusBlur);\n    chrome.runtime.onMessage.addListener(handleRequest);\n\n    infodiv_ = document.getElementById('info');\n    statusdiv_ = document.getElementById('status');\n    warningdiv_ = document.getElementById('warning');\n    errordiv_ = document.getElementById('error');\n\n    infodiv_.removeChild(warningdiv_);\n    infodiv_.removeChild(errordiv_);\n\n    warningdiv_.style.display = 'block';\n    errordiv_.style.display = 'block';\n\n    setDebug(debug_);\n    setHiDPI(hidpi_);\n    setTitle(title_);\n\n    registerWindow(true);\n});\n"
  },
  {
    "path": "host-ext/gencrx.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# This script generates a crx extension package, and is meant to be used by\n# developers: Users should download the extension from the Web Store.\n#\n# Prerequistes:\n#  - NaCl SDK. Path specified with NACL_SDK_ROOT (e.g. ~/naclsdk/pepper_35)\n#  - zip, openssl\n#\n# This code is loosely based on a script found along the CRX file format\n# specification: http://developer.chrome.com/extensions/crx.html\n\nset -e\n\nEXTNAME=\"crouton\"\nCRIAT_PEXE=\"$EXTNAME/kiwi.pexe\"\n\ncd \"`dirname \"$0\"`\"\n\nrm -f \"$EXTNAME.crx\" \"$EXTNAME.zip\"\n\ntrap \"rm -f '$EXTNAME.sig' '$EXTNAME.pub'\" 0\n\nrm -f \"$CRIAT_PEXE\"\n# Build NaCl module\nmake -C nacl_src clean\nmake -C nacl_src\nif [ ! -f \"$CRIAT_PEXE\" ]; then\n    echo \"$CRIAT_PEXE not created as expected\" 1>&2\n    exit 1\nfi\n\n# Create zip file\n( cd $EXTNAME; zip -qr -9 -X \"../$EXTNAME.zip\" . )\n\nif [ ! -e \"$EXTNAME.pem\" ]; then\n    openssl genrsa -out \"$EXTNAME.pem\" 1024\nfi\n\n# Signature\nopenssl sha1 -sha1 -binary -sign \"$EXTNAME.pem\" <\"$EXTNAME.zip\" >\"$EXTNAME.sig\"\n\n# Public key\nopenssl rsa -pubout -outform DER < \"$EXTNAME.pem\" >\"$EXTNAME.pub\" 2>/dev/null\n\n# Print a 32-bit integer, Little-endian byte order\nprintint() {\n    val=\"$1\"\n    i=4\n    while [ $i -gt 0 ]; do\n        lsb=\"$(($val % 256))\"\n        oct=\"`printf '%o' $lsb`\"\n        printf \"\\\\$oct\"\n        val=\"$(($val / 256))\"\n        i=\"$(($i-1))\"\n    done\n}\n\n{\n    # Magic number\n    echo -n 'Cr24'\n    # Version\n    printint 2\n    # Public key length\n    printint \"`stat -c'%s' \"$EXTNAME.pub\"`\"\n    # Signature length\n    printint \"`stat -c'%s' \"$EXTNAME.sig\"`\"\n    cat \"$EXTNAME.pub\" \"$EXTNAME.sig\" \"$EXTNAME.zip\"\n} > \"$EXTNAME.crx\"\n"
  },
  {
    "path": "host-ext/nacl_src/.gitignore",
    "content": "pnacl\n"
  },
  {
    "path": "host-ext/nacl_src/Makefile",
    "content": "# Copyright (c) 2016 The Chromium Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# GNU Makefile based on shared rules provided by the Native Client SDK.\n# See README.Makefiles for more details.\n\nVALID_TOOLCHAINS := pnacl\n\nNACL_SDK_ROOT ?= $(shell find ../.. -type d -regex '.*/nacl_sdk/pepper_[0-9]+' \\\n\t\t\t\t             | sort | head -n1)\n\n../crouton/kiwi.pexe: pnacl/Release/kiwi.pexe\n\tcp pnacl/Release/kiwi.pexe ../crouton/kiwi.pexe\n\ninclude $(NACL_SDK_ROOT)/tools/common.mk\n\n\nTARGET = kiwi\nLIBS = ppapi_cpp ppapi\n\nCFLAGS = -Wall -std=gnu++11\nSOURCES = kiwi.cc\n\n# Build rules generated by macros from common.mk:\n$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))\n\nifeq ($(CONFIG),Release)\n$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))\n$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))\nelse\n$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))\nendif\n\n$(eval $(call NMF_RULE,$(TARGET),))\n"
  },
  {
    "path": "host-ext/nacl_src/keycode_converter.h",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Translates NaCl pp::KeyboardInputEvent::KeyCode() strings to X11 keycodes.\n */\n\n#include <map>\n#include <string>\n#include <stdint.h>\n\n/* Values in the keycode hashmap. */\nclass KeyCode {\npublic:\n    KeyCode(uint8_t base, uint8_t search):\n        base_(base), search_(search) {}\n\n    explicit KeyCode(uint8_t base): KeyCode(base, base) {}\n\n    uint8_t GetCode(const bool search_on) const {\n        if (search_on)\n            return search_;\n        else\n            return base_;\n    }\n\nprivate:\n    const uint8_t base_;  /* Basic keycode */\n    /* Reverse translation of keycode when Search is pressed: e.g.\n     * Search+Left => Home. In this case:\n     * base_ = Home keycode (0x6e), search_ = Left keycode (0x71) */\n    const uint8_t search_;\n};\n\n/* Class with static members only: converts KeyCode string to X11 keycode */\nclass KeyCodeConverter {\npublic:\n    static uint8_t GetCode(const std::string& str, const bool search_on) {\n        if (!strcodemap_)\n            InitMap();\n\n        auto it = strcodemap_->find(str);\n        /* Not found */\n        if (it == strcodemap_->end())\n            return 0;\n\n        return it->second.GetCode(search_on);\n    }\n\nprivate:\n    static void InitMap();\n    /* static pointer member, to avoid static variable of class type. */\n    static std::map<std::string, const KeyCode>* strcodemap_;\n};\n\nstd::map<std::string, const KeyCode>* KeyCodeConverter::strcodemap_ = NULL;\n\n/* Initialize string to X11 keycode mapping. Must be called once only.\n *\n * FIXME: Fill in search_ fields in KeyCode.\n *\n * Most of this data can be generated from\n * ui/events/keycodes/dom4/keycode_converter_data.h in the Chromium source\n * tree, using something like:\n * sed -n \\\n's/.*USB_KEYMAP([^,]*, \\([^,]*\\),.*, \\(\"[^\"]*\"\\).*$/{\\2, KeyCode(\\1)},/p' \\\nkeycode_converter_data.h | grep -v \"0x00\" >> keymap_data.h\n */\nvoid KeyCodeConverter::InitMap() {\n    strcodemap_ = new std::map<std::string, const KeyCode>({\n        {\"Sleep\", KeyCode(0x96)},\n        {\"WakeUp\", KeyCode(0x97)},\n        {\"KeyA\", KeyCode(0x26)},\n        {\"KeyB\", KeyCode(0x38)},\n        {\"KeyC\", KeyCode(0x36)},\n        {\"KeyD\", KeyCode(0x28)},\n        {\"KeyE\", KeyCode(0x1a)},\n        {\"KeyF\", KeyCode(0x29)},\n        {\"KeyG\", KeyCode(0x2a)},\n        {\"KeyH\", KeyCode(0x2b)},\n        {\"KeyI\", KeyCode(0x1f)},\n        {\"KeyJ\", KeyCode(0x2c)},\n        {\"KeyK\", KeyCode(0x2d)},\n        {\"KeyL\", KeyCode(0x2e)},\n        {\"KeyM\", KeyCode(0x3a)},\n        {\"KeyN\", KeyCode(0x39)},\n        {\"KeyO\", KeyCode(0x20)},\n        {\"KeyP\", KeyCode(0x21)},\n        {\"KeyQ\", KeyCode(0x18)},\n        {\"KeyR\", KeyCode(0x1b)},\n        {\"KeyS\", KeyCode(0x27)},\n        {\"KeyT\", KeyCode(0x1c)},\n        {\"KeyU\", KeyCode(0x1e)},\n        {\"KeyV\", KeyCode(0x37)},\n        {\"KeyW\", KeyCode(0x19)},\n        {\"KeyX\", KeyCode(0x35)},\n        {\"KeyY\", KeyCode(0x1d)},\n        {\"KeyZ\", KeyCode(0x34)},\n        {\"Digit1\", KeyCode(0x0a)},\n        {\"Digit2\", KeyCode(0x0b)},\n        {\"Digit3\", KeyCode(0x0c)},\n        {\"Digit4\", KeyCode(0x0d)},\n        {\"Digit5\", KeyCode(0x0e)},\n        {\"Digit6\", KeyCode(0x0f)},\n        {\"Digit7\", KeyCode(0x10)},\n        {\"Digit8\", KeyCode(0x11)},\n        {\"Digit9\", KeyCode(0x12)},\n        {\"Digit0\", KeyCode(0x13)},\n        {\"Enter\", KeyCode(0x24)},\n        {\"Escape\", KeyCode(0x09)},\n        {\"Backspace\", KeyCode(0x16)},\n        {\"Tab\", KeyCode(0x17)},\n        {\"Space\", KeyCode(0x41)},\n        {\"Minus\", KeyCode(0x14)},\n        {\"Equal\", KeyCode(0x15)},\n        {\"BracketLeft\", KeyCode(0x22)},\n        {\"BracketRight\", KeyCode(0x23)},\n        {\"Backslash\", KeyCode(0x33)},\n        {\"IntlHash\", KeyCode(0x33)},\n        {\"Semicolon\", KeyCode(0x2f)},\n        {\"Quote\", KeyCode(0x30)},\n        {\"Backquote\", KeyCode(0x31)},\n        {\"Comma\", KeyCode(0x3b)},\n        {\"Period\", KeyCode(0x3c)},\n        {\"Slash\", KeyCode(0x3d)},\n        {\"CapsLock\", KeyCode(0x42)},\n        {\"F1\", KeyCode(0x43)},\n        {\"F2\", KeyCode(0x44)},\n        {\"F3\", KeyCode(0x45)},\n        {\"F4\", KeyCode(0x46)},\n        {\"F5\", KeyCode(0x47)},\n        {\"F6\", KeyCode(0x48)},\n        {\"F7\", KeyCode(0x49)},\n        {\"F8\", KeyCode(0x4a)},\n        {\"F9\", KeyCode(0x4b)},\n        {\"F10\", KeyCode(0x4c)},\n        {\"F11\", KeyCode(0x5f)},\n        {\"F12\", KeyCode(0x60)},\n        {\"PrintScreen\", KeyCode(0x6b)},\n        {\"ScrollLock\", KeyCode(0x4e)},\n        {\"Pause\", KeyCode(0x7f)},\n        {\"Insert\", KeyCode(0x76)},\n        {\"Home\", KeyCode(0x6e)},\n        {\"PageUp\", KeyCode(0x70)},\n        {\"Delete\", KeyCode(0x77)},\n        {\"End\", KeyCode(0x73)},\n        {\"PageDown\", KeyCode(0x75)},\n        {\"ArrowRight\", KeyCode(0x72)},\n        {\"ArrowLeft\", KeyCode(0x71)},\n        {\"ArrowDown\", KeyCode(0x74)},\n        {\"ArrowUp\", KeyCode(0x6f)},\n        {\"NumLock\", KeyCode(0x4d)},\n        {\"NumpadDivide\", KeyCode(0x6a)},\n        {\"NumpadMultiply\", KeyCode(0x3f)},\n        {\"NumpadSubtract\", KeyCode(0x52)},\n        {\"NumpadAdd\", KeyCode(0x56)},\n        {\"NumpadEnter\", KeyCode(0x68)},\n        {\"Numpad1\", KeyCode(0x57)},\n        {\"Numpad2\", KeyCode(0x58)},\n        {\"Numpad3\", KeyCode(0x59)},\n        {\"Numpad4\", KeyCode(0x53)},\n        {\"Numpad5\", KeyCode(0x54)},\n        {\"Numpad6\", KeyCode(0x55)},\n        {\"Numpad7\", KeyCode(0x4f)},\n        {\"Numpad8\", KeyCode(0x50)},\n        {\"Numpad9\", KeyCode(0x51)},\n        {\"Numpad0\", KeyCode(0x5a)},\n        {\"NumpadDecimal\", KeyCode(0x5b)},\n        {\"IntlBackslash\", KeyCode(0x5e)},\n        {\"ContextMenu\", KeyCode(0x87)},\n        {\"Power\", KeyCode(0x7c)},\n        {\"NumpadEqual\", KeyCode(0x7d)},\n        {\"Help\", KeyCode(0x92)},\n        {\"Again\", KeyCode(0x89)},\n        {\"Undo\", KeyCode(0x8b)},\n        {\"Cut\", KeyCode(0x91)},\n        {\"Copy\", KeyCode(0x8d)},\n        {\"Paste\", KeyCode(0x8f)},\n        {\"Find\", KeyCode(0x90)},\n        {\"VolumeMute\", KeyCode(0x79)},\n        {\"VolumeUp\", KeyCode(0x7b)},\n        {\"VolumeDown\", KeyCode(0x7a)},\n        {\"IntlRo\", KeyCode(0x61)},\n        {\"KanaMode\", KeyCode(0x65)},\n        {\"IntlYen\", KeyCode(0x84)},\n        {\"Convert\", KeyCode(0x64)},\n        {\"NonConvert\", KeyCode(0x66)},\n        {\"Lang1\", KeyCode(0x82)},\n        {\"Lang2\", KeyCode(0x83)},\n        {\"Lang3\", KeyCode(0x62)},\n        {\"Lang4\", KeyCode(0x63)},\n        {\"Abort\", KeyCode(0x88)},\n        {\"NumpadParenLeft\", KeyCode(0xbb)},\n        {\"NumpadParenRight\", KeyCode(0xbc)},\n        {\"ControlLeft\", KeyCode(0x25)},\n        {\"ShiftLeft\", KeyCode(0x32)},\n        {\"AltLeft\", KeyCode(0x40)},\n        {\"OSLeft\", KeyCode(0x85)},\n        {\"ControlRight\", KeyCode(0x69)},\n        {\"ShiftRight\", KeyCode(0x3e)},\n        {\"AltRight\", KeyCode(0x6c)},\n        {\"OSRight\", KeyCode(0x86)},\n        {\"BrightnessUp\", KeyCode(0xe9)},\n        {\"BrightnessDown\", KeyCode(0xea)},\n        {\"LaunchApp2\", KeyCode(0x94)},\n        {\"LaunchApp1\", KeyCode(0xa5)},\n        {\"BrowserBack\", KeyCode(0xa6)},\n        {\"BrowserForward\", KeyCode(0xa7)},\n        {\"BrowserRefresh\", KeyCode(0xb5)},\n        {\"BrowserFavorites\", KeyCode(0xa4)},\n        {\"MailReply\", KeyCode(0xf0)},\n        {\"MailForward\", KeyCode(0xf1)},\n        {\"MailSend\", KeyCode(0xef)},\n    });\n}\n"
  },
  {
    "path": "host-ext/nacl_src/kiwi.cc",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * This is a NaCl module, used by the crouton extension, to provide\n * a display for crouton-in-a-tab.\n * On one end, it communicates with the Javascript module window.js, on the\n * other, it requests, via WebSocket, frames from croutonfbserver, and sends\n * inputs events.\n *\n */\n\n#include <sstream>\n#include <unordered_map>\n\n#include \"ppapi/cpp/graphics_2d.h\"\n#include \"ppapi/cpp/image_data.h\"\n#include \"ppapi/cpp/input_event.h\"\n#include \"ppapi/cpp/instance.h\"\n#include \"ppapi/cpp/message_loop.h\"\n#include \"ppapi/cpp/module.h\"\n#include \"ppapi/cpp/mouse_cursor.h\"\n#include \"ppapi/cpp/point.h\"\n#include \"ppapi/cpp/var.h\"\n#include \"ppapi/cpp/var_array_buffer.h\"\n#include \"ppapi/cpp/websocket.h\"\n#include \"ppapi/utility/completion_callback_factory.h\"\n\n/* Protocol data structures */\n#include \"../../src/fbserver-proto.h\"\n\n#include \"keycode_converter.h\"\n\nclass KiwiInstance : public pp::Instance {\npublic:\n    explicit KiwiInstance(PP_Instance instance): pp::Instance(instance) {}\n\n    virtual ~KiwiInstance() {}\n\n    /* Registers events */\n    virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {\n        RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE |\n                           PP_INPUTEVENT_CLASS_WHEEL |\n                           PP_INPUTEVENT_CLASS_TOUCH |\n                           PP_INPUTEVENT_CLASS_IME);\n        RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);\n\n        srand(pp::Module::Get()->core()->GetTime());\n\n        return true;\n    }\n\n    /** Interface with Javascript **/\npublic:\n    /* Handles message from Javascript\n     * Format: <type>:<str> */\n    virtual void HandleMessage(const pp::Var& var_message) {\n        if (!var_message.is_string())\n            return;\n\n        std::string message = var_message.AsString();\n\n        LogMessage(2) << \"message=\" << message;\n\n        size_t pos = message.find(':');\n        if (pos != std::string::npos) {\n            std::string type = message.substr(0, pos);\n            if (type == \"resize\") {\n                size_t pos2 = message.find('/', pos+1);\n                if (pos2 != std::string::npos) {\n                    int width = stoi(message.substr(pos+1, pos2-pos-1));\n                    int height = stoi(message.substr(pos2+1));\n                    ChangeResolution(width*scale_*view_css_scale_ + 0.5,\n                                     height*scale_*view_css_scale_ + 0.5);\n                }\n            } else if (type == \"display\") {\n                int display = stoi(message.substr(pos+1));\n                if (display != display_) {\n                    display_ = display;\n                    SocketConnect();\n                }\n            } else if (type == \"blur\" || type == \"hide\") {\n                /* Release all keys */\n                SocketSend(pp::Var(\"Q\"), false);\n                /* Throttle/stop refresh */\n                SetTargetFPS((type == \"blur\") ? kBlurFPS : kHiddenFPS);\n            } else if (type == \"focus\") {\n                /* Force refresh and ask for next frame */\n                SetTargetFPS(kFullFPS);\n            } else if (type == \"debug\") {\n                debug_ = stoi(message.substr(pos+1));\n            } else if (type == \"hidpi\") {\n                bool newhidpi = stoi(message.substr(pos+1));\n                if (newhidpi != hidpi_) {\n                    hidpi_ = newhidpi;\n                    InitContext();\n                }\n            }\n        }\n    }\n\nprivate:\n    /* Message class that allows C++-style logging/status messages.\n     * The message is flushed when the object gets out of scope. */\n    class Message {\n    public:\n        Message(pp::Instance* inst, const std::string& type, bool dummy):\n            inst_(inst) {\n            if (!dummy) {\n                out_.reset(new std::ostringstream());\n                *out_ << type << \":\";\n            }\n        }\n\n        virtual ~Message() {\n            if (out_) inst_->PostMessage(out_->str());\n        }\n\n        template<typename T> Message& operator<<(const T& val) {\n            if (out_) *out_ << val;\n            return *this;\n        }\n\n        Message(Message&& other) = default;  /* Steals the unique_ptr */\n\n        /* The next 2 functions cannot be implemented correctly, make sure we\n         * cannot call them */\n        Message(const Message& other) = delete;\n        Message& operator =(const Message&) = delete;\n\n    private:\n        std::unique_ptr<std::ostringstream> out_;\n        pp::Instance* inst_;\n    };\n\n    /* Sends a status message to Javascript */\n    Message StatusMessage() {\n        return Message(this, \"status\", false);\n    }\n\n    /* Sends a warning message to Javascript */\n    Message WarningMessage() {\n        return Message(this, \"warning\", false);\n    }\n\n    /* Sends an error message to Javascript: all errors are fatal and a\n     * disconnect message will be sent soon after. */\n    Message ErrorMessage() {\n        return Message(this, \"error\", false);\n    }\n\n    /* Sends a logging message to Javascript */\n    Message LogMessage(int level) {\n        if (level <= debug_) {\n            double delta = 1000 *\n                (pp::Module::Get()->core()->GetTime() - lasttime_);\n            Message m(this, \"debug\", false);\n            m << \"(\" << level << \") \" << (int)delta << \" \";\n            return m;\n        } else {\n            return Message(this, \"debug\", true);\n        }\n    }\n\n    /* Sends a resize message to Javascript, divide width & height by scale */\n    void ResizeMessage(int width, int height, float scale) {\n        Message(this, \"resize\", false) << (int)(width/scale + 0.5) << \"/\"\n                                       << (int)(height/scale + 0.5);\n    }\n\n    /* Sends a control message to Javascript\n     * Format: <type>:<str> */\n    void ControlMessage(const std::string& type, const std::string& str) {\n        Message(this, type, false) << str;\n    }\n\n    /** WebSocket interface **/\nprivate:\n    /* Connects to WebSocket server\n     * Parameter is ignored: used for callbacks */\n    void SocketConnect(int32_t /*result*/ = 0) {\n        if (display_ < 0) {\n            ErrorMessage() << \"SocketConnect: No display defined yet.\";\n            return;\n        }\n\n        std::ostringstream url;\n        url << \"ws://localhost:\" << (PORT_BASE + display_) << \"/\";\n        websocket_.reset(new pp::WebSocket(this));\n        websocket_->Connect(pp::Var(url.str()), NULL, 0,\n                            callback_factory_.NewCallback(\n                                &KiwiInstance::OnSocketConnectCompletion));\n        StatusMessage() << \"Connecting...\";\n    }\n\n    /* Called when WebSocket is connected (or failed to connect) */\n    void OnSocketConnectCompletion(int32_t result) {\n        if (result != PP_OK) {\n            retry_++;\n            if (retry_ < kMaxRetry) {\n                StatusMessage() << \"Connection failed with code \" << result\n                                << \", \" << retry_ << \" attempt(s). Retrying...\";\n                pp::Module::Get()->core()->CallOnMainThread(1000,\n                   callback_factory_.NewCallback(&KiwiInstance::SocketConnect));\n            } else {\n                ErrorMessage() << \"Connection failed (code: \" << result << \").\";\n                ControlMessage(\"disconnected\", \"Connection failed\");\n            }\n\n            return;\n        }\n\n        cursor_cache_.clear();\n\n        SocketReceive();\n\n        StatusMessage() << \"Connected.\";\n    }\n\n    /* Closes the WebSocket connection. */\n    void SocketClose(const std::string& reason) {\n        websocket_->Close(0, pp::Var(reason),\n            callback_factory_.NewCallback(&KiwiInstance::OnSocketClosed));\n    }\n\n    /* Called when WebSocket is closed */\n    void OnSocketClosed(int32_t result) {\n        StatusMessage() << \"Disconnected...\";\n        ControlMessage(\"disconnected\", \"Socket closed\");\n        connected_ = false;\n        screen_flying_ = false;\n        Paint(true);\n    }\n\n    /* Checks if a WebSocket request size is valid:\n     *  - length: payload length\n     *  - target: expected length for request type\n     *  - type: request type, to be printed on error\n     */\n    bool CheckSize(int length, int target, const std::string& type) {\n        if (length == target)\n            return true;\n\n        ErrorMessage() << \"Invalid \" << type << \" request (\" << length\n                       << \" != \" << target << \").\";\n        return false;\n    }\n\n    /* Recieves and processes initialization information */\n    bool SocketParseInitInformation(const char* data, int datalen) {\n        if (!CheckSize(datalen, sizeof(struct initinfo), \"Init information\"))\n            return false;\n\n        initinfo* info = (struct initinfo*) data;\n        freon_ = info->freon;\n        return true;\n    }\n\n    /* Receives and handles a version request */\n    bool SocketParseVersion(const char* data, int datalen) {\n        if (connected_) {\n            ErrorMessage() << \"Received a version while already connected.\";\n            return false;\n        }\n\n        server_version_ = data;\n\n        if (server_version_ != VERSION) {\n            /* TODO: Remove VF1 compatiblity */\n            if (server_version_ == \"VF1\" || server_version_ == \"VF2\") {\n                WarningMessage() << \"Outdated server version (\"\n                                 << server_version_ << \"), expecting \" << VERSION\n                                 << \". Please update your chroot.\";\n            } else {\n                ErrorMessage() << \"Invalid server version (\"\n                               << server_version_ << \"), expecting \" << VERSION\n                               << \". Please update your chroot.\";\n                return false;\n            }\n        }\n\n        connected_ = true;\n        SocketSend(pp::Var(\"VOK\"), false);\n        ControlMessage(\"connected\", \"Version received\");\n        ChangeResolution(size_.width(), size_.height());\n        /* Start requesting frames */\n        OnFlush();\n        return true;\n    }\n\n    /* Receives and handles a screen_reply request */\n    bool SocketParseScreen(const char* data, int datalen) {\n        if (!CheckSize(datalen, sizeof(struct screen_reply), \"screen_reply\"))\n            return false;\n\n        struct screen_reply* reply = (struct screen_reply*)data;\n        if (reply->updated) {\n            if (!reply->shmfailed) {\n                Paint(false);\n            } else {\n                /* Blank the frame if shm failed */\n                Paint(true);\n                force_refresh_ = true;\n            }\n        } else {\n            screen_flying_ = false;\n            /* No update: Ask for next frame in 1000/target_fps_ */\n            if (target_fps_ > 0) {\n                pp::Module::Get()->core()->CallOnMainThread(\n                    1000/target_fps_,\n                    callback_factory_.NewCallback(\n                        &KiwiInstance::RequestScreen),\n                    request_token_);\n            }\n        }\n\n        if (reply->cursor_updated) {\n            /* Cursor updated: find it in cache */\n            std::unordered_map<uint32_t, Cursor>::iterator it =\n                cursor_cache_.find(reply->cursor_serial);\n            if (it == cursor_cache_.end()) {\n                /* No cache entry, ask for data. */\n                SocketSend(pp::Var(\"P\"), false);\n            } else {\n                LogMessage(2) << \"Cursor use cache for \"\n                              << reply->cursor_serial;\n                pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM,\n                                           it->second.img, it->second.hot);\n            }\n        }\n        return true;\n    }\n\n    /* Receives and handles a cursor_reply request */\n    bool SocketParseCursor(const char* data, int datalen) {\n        if (datalen < sizeof(struct cursor_reply)) {\n            ErrorMessage() << \"Invalid cursor_reply packet (\" << datalen\n                           << \" < \" << sizeof(struct cursor_reply) << \").\";\n            return false;\n        }\n\n        struct cursor_reply* cursor = (struct cursor_reply*)data;\n        if (!CheckSize(datalen,\n                       sizeof(struct cursor_reply) +\n                           4*cursor->width*cursor->height,\n                       \"cursor_reply\"))\n            return false;\n\n        LogMessage(0) << \"Cursor \"\n                      << (cursor->width) << \"/\" << (cursor->height)\n                      << \" \" << (cursor->xhot) << \"/\" << (cursor->yhot)\n                      << \" \" << (cursor->cursor_serial);\n\n        /* Scale down if needed */\n        int scale = 1;\n        while (cursor->width/scale > 32 || cursor->height/scale > 32)\n            scale *= 2;\n\n        int w = cursor->width/scale;\n        int h = cursor->height/scale;\n        pp::ImageData img(this, pp::ImageData::GetNativeImageDataFormat(),\n                          pp::Size(w, h), true);\n        uint32_t* imgdata = (uint32_t*)img.data();\n        for (int y = 0; y < h; y++) {\n            for (int x = 0; x < w; x++) {\n                /* Nearest neighbour is least ugly */\n                imgdata[y*w + x] = cursor->pixels[scale*y*scale*w + scale*x];\n            }\n        }\n        pp::Point hot(cursor->xhot/scale, cursor->yhot/scale);\n\n        cursor_cache_[cursor->cursor_serial].img = img;\n        cursor_cache_[cursor->cursor_serial].hot = hot;\n        pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM,\n                                       img, hot);\n        return true;\n    }\n\n    /* Receives and handles a resolution request */\n    bool SocketParseResolution(const char* data, int datalen) {\n        if (!CheckSize(datalen, sizeof(struct resolution), \"resolution\"))\n            return false;\n        struct resolution* r = (struct resolution*)data;\n        /* Tell Javascript so that it can center us on the page */\n        ResizeMessage(r->width, r->height, scale_*view_css_scale_);\n        force_refresh_ = true;\n        return true;\n    }\n\n    /* Called when a frame is received from WebSocket server */\n    void OnSocketReceiveCompletion(int32_t result) {\n        LogMessage(5) << \"ReadCompletion: \" << result << \".\";\n\n        if (result == PP_ERROR_INPROGRESS) {\n            LogMessage(0) << \"Receive error INPROGRESS (should not happen).\";\n            /* We called SocketReceive too many times. */\n            /* Not fatal: just wait for next call */\n            return;\n        } else if (result != PP_OK) {\n            /* FIXME: Receive error is \"normal\" when fbserver exits. */\n            LogMessage(-1) << \"Receive error.\";\n            SocketClose(\"Receive error.\");\n            return;\n        }\n\n        /* Get ready to receive next frame */\n        pp::Module::Get()->core()->CallOnMainThread(0,\n                  callback_factory_.NewCallback(&KiwiInstance::SocketReceive));\n\n        /* Convert binary/text to char* */\n        const char* data;\n        int datalen;\n        std::string str;\n        if (receive_var_.is_array_buffer()) {\n            pp::VarArrayBuffer array_buffer(receive_var_);\n            data = static_cast<char*>(array_buffer.Map());\n            datalen = array_buffer.ByteLength();\n            LogMessage(data[0] == 'S' ? 3 : 2) << \"receive (binary): \"\n                                               << data[0];\n        } else {\n            str = receive_var_.AsString();\n            LogMessage(3) << \"receive (text): \" << str;\n            data = str.c_str();\n            datalen = str.length();\n        }\n\n        if (data[0] == 'V') {  /* Version */\n            if (!SocketParseVersion(data, datalen))\n                SocketClose(\"Incorrect version.\");\n\n            return;\n        }\n\n        if (connected_) {\n            switch (data[0]) {\n            case 'S':  /* Screen */\n                if (SocketParseScreen(data, datalen)) return;\n                break;\n            case 'P':  /* New cursor data is received */\n                if (SocketParseCursor(data, datalen)) return;\n                break;\n            case 'R':  /* Resolution request reply */\n                if (SocketParseResolution(data, datalen)) return;\n                break;\n            case 'I':  /* Init information */\n                if (SocketParseInitInformation(data, datalen)) return;\n                break;\n            default:\n                ErrorMessage() << \"Invalid request. First char: \"\n                               << (int)data[0];\n                /* Fall-through: disconnect. */\n            }\n        } else {\n            ErrorMessage() << \"Got some packet before version...\";\n        }\n\n        SocketClose(\"Invalid payload.\");\n    }\n\n    /* Asks to receive the next WebSocket frame\n     * Parameter is ignored: used for callbacks */\n    void SocketReceive(int32_t /*result*/ = 0) {\n        websocket_->ReceiveMessage(&receive_var_, callback_factory_.NewCallback(\n                &KiwiInstance::OnSocketReceiveCompletion));\n    }\n\n    /* Sends a WebSocket request, possibly flushing current mouse position\n     * first */\n    void SocketSend(const pp::Var& var, bool flushmouse) {\n        if (!connected_) {\n            LogMessage(-1) << \"SocketSend: not connected!\";\n            return;\n        }\n\n        if (pending_mouse_move_ && flushmouse) {\n            struct mousemove* mm;\n            pp::VarArrayBuffer array_buffer(sizeof(*mm));\n            mm = static_cast<struct mousemove*>(array_buffer.Map());\n            mm->type = 'M';\n            mm->x = mouse_pos_.x();\n            mm->y = mouse_pos_.y();\n            array_buffer.Unmap();\n            websocket_->SendMessage(array_buffer);\n            pending_mouse_move_ = false;\n        }\n\n        websocket_->SendMessage(var);\n    }\n\n    /** UI functions **/\npublic:\n    /* Called when the NaCl module view changes (size, visibility) */\n    virtual void DidChangeView(const pp::View& view) {\n        view_device_scale_ = view.GetDeviceScale();\n        view_css_scale_ = view.GetCSSScale();\n        view_rect_ = view.GetRect();\n        InitContext();\n    }\n\n    /* Called when an input event is received */\n    virtual bool HandleInputEvent(const pp::InputEvent& event) {\n        if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN ||\n            event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) {\n            pp::KeyboardInputEvent key_event(event);\n\n            uint32_t jskeycode = key_event.GetKeyCode();\n            std::string keystr = key_event.GetCode().AsString();\n            bool down = event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN;\n\n            if (jskeycode == 183) {  /* Fullscreen => toggle fullscreen */\n                if (!down)\n                    ControlMessage(\"state\", \"fullscreen\");\n                return PP_TRUE;\n            } else if (jskeycode == 182) {  /* Page flipper => minimize window */\n                if (!down)\n                    ControlMessage(\"state\", \"hide\");\n                return PP_TRUE;\n            }\n\n            /* TODO: Reverse Search key translation when appropriate */\n            uint8_t keycode = KeyCodeConverter::GetCode(keystr, false);\n            /* TODO: Remove VF1 compatibility */\n            uint32_t keysym = 0;\n            if (server_version_ == \"VF1\")\n                keysym = KeyCodeToKeySym(jskeycode, keystr);\n\n            LogMessage(keycode == 0 ? 0 : 1)\n                << \"Key \" << (down ? \"DOWN\" : \"UP\")\n                << \": C:\" << keystr\n                << \", JSKC:\" << std::hex << jskeycode\n                << \" => KC:\" << (int)keycode\n                << (keycode == 0 ? \" (KEY UNKNOWN!)\" : \"\")\n                << \" searchstate:\" << search_state_;\n\n            if (keycode == 0 && keysym == 0) {\n                return PP_TRUE;\n            }\n\n            if (!freon_) {\n                /* We delay sending Super-L, and only \"press\" it on mouse clicks and\n                 * letter keys (a-z). This way, Home (Search+Left) appears without\n                 * modifiers (instead of Super_L+Home) */\n                if (keystr == \"OSLeft\") {\n                    if (down) {\n                        search_state_ = kSearchUpFirst;\n                    } else {\n                        if (search_state_ == kSearchUpFirst) {\n                            /* No other key was pressed: press+release */\n                            SendSearchKey(1);\n                            SendSearchKey(0);\n                        } else if (search_state_ == kSearchDown) {\n                            SendSearchKey(0);\n                        }\n                        search_state_ = kSearchInactive;\n                    }\n                    return PP_TRUE;  /* Ignore key */\n                }\n\n                if (jskeycode >= 65 && jskeycode <= 90) {  /* letter */\n                    /* Search is active, send Super_L if needed */\n                    if (down && (search_state_ == kSearchUpFirst ||\n                                 search_state_ == kSearchUp)) {\n                        SendSearchKey(1);\n                        search_state_ = kSearchDown;\n                    }\n                } else {  /* non-letter */\n                    /* Release Super_L if needed */\n                    if (search_state_ == kSearchDown) {\n                        SendSearchKey(0);\n                        search_state_ = kSearchUp;\n                    } else if (search_state_ == kSearchUpFirst) {\n                        /* Switch from UpFirst to Up */\n                        search_state_ = kSearchUp;\n                    }\n                }\n            }\n            if (server_version_ == \"VF1\")\n                SendKeySym(keysym, down ? 1 : 0);\n            else\n                SendKeyCode(keycode, down ? 1 : 0);\n        } else if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN ||\n                   event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP   ||\n                   event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE) {\n            pp::MouseInputEvent mouse_event(event);\n            pp::Point mouse_event_pos(\n                mouse_event.GetPosition().x() * scale_,\n                mouse_event.GetPosition().y() * scale_);\n            bool down = event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN;\n\n            if (mouse_pos_.x() != mouse_event_pos.x() ||\n        \tmouse_pos_.y() != mouse_event_pos.y()) {\n                pending_mouse_move_ = true;\n                mouse_pos_ = mouse_event_pos;\n            }\n\n            Message m = LogMessage(3);\n            m << \"Mouse \" << mouse_event_pos.x() << \"x\"\n              << mouse_event_pos.y();\n\n            if (event.GetType() != PP_INPUTEVENT_TYPE_MOUSEMOVE) {\n                m << \" \" << (down ? \"DOWN\" : \"UP\")\n                  << \" \" << (mouse_event.GetButton());\n\n                /* SendClick calls SocketSend, which flushes the mouse position\n                 * before sending the click event.\n                 * Also, Javascript button numbers are 0-based (left=0), while\n                 * X11 numbers are 1-based (left=1). */\n                SendClick(mouse_event.GetButton() + 1, down ? 1 : 0);\n            }\n        } else if (event.GetType() == PP_INPUTEVENT_TYPE_WHEEL) {\n            pp::WheelInputEvent wheel_event(event);\n\n            mouse_wheel_x += wheel_event.GetDelta().x();\n            mouse_wheel_y += wheel_event.GetDelta().y();\n\n            LogMessage(2) << \"MWd \" << wheel_event.GetDelta().x() << \"x\"\n                                    << wheel_event.GetDelta().y()\n                          << \"MWt \" << wheel_event.GetTicks().x() << \"x\"\n                                    << wheel_event.GetTicks().y()\n                          << \"acc \" << mouse_wheel_x << \"x\"\n                                    << mouse_wheel_y;\n\n            while (mouse_wheel_x <= -16) {\n                SendClick(6, 1); SendClick(6, 0);\n                mouse_wheel_x += 16;\n            }\n            while (mouse_wheel_x >= 16) {\n                SendClick(7, 1); SendClick(7, 0);\n                mouse_wheel_x -= 16;\n            }\n\n            while (mouse_wheel_y <= -16) {\n                SendClick(5, 1); SendClick(5, 0);\n                mouse_wheel_y += 16;\n            }\n            while (mouse_wheel_y >= 16) {\n                SendClick(4, 1); SendClick(4, 0);\n                mouse_wheel_y -= 16;\n            }\n        } else if (event.GetType() == PP_INPUTEVENT_TYPE_TOUCHSTART ||\n                   event.GetType() == PP_INPUTEVENT_TYPE_TOUCHMOVE ||\n                   event.GetType() == PP_INPUTEVENT_TYPE_TOUCHEND) {\n            /* FIXME: This is a very primitive implementation:\n             * we only handle single touch */\n\n            pp::TouchInputEvent touch_event(event);\n\n            int count = touch_event.GetTouchCount(\n                PP_TOUCHLIST_TYPE_CHANGEDTOUCHES);\n\n            Message m = LogMessage(2);\n            m << \"TOUCH \" << count << \" \";\n\n            /* We only care about the first touch (when count goes from 0\n             * to 1), and record the id in touch_id_. */\n            switch (event.GetType()) {\n            case PP_INPUTEVENT_TYPE_TOUCHSTART:\n                if (touch_count_ == 0 && count == 1) {\n                    touch_id_ = touch_event.GetTouchByIndex(\n                        PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, 0).id();\n                }\n                touch_count_ += count;\n                m << \"START\";\n                break;\n            case PP_INPUTEVENT_TYPE_TOUCHMOVE:\n                m << \"MOVE\";\n                break;\n            case PP_INPUTEVENT_TYPE_TOUCHEND:\n                touch_count_ -= count;\n                m << \"END\";\n                break;\n            default:\n                break;\n            }\n\n            /* FIXME: Is there a better way to figure out if a touch id\n             * is present? (GetTouchById is unhelpful and returns a TouchPoint\n             * full of zeros, which may well be valid...) */\n            bool has_tpid = false;\n            for (int i = 0; i < count; i++) {\n                pp::TouchPoint tp = touch_event.GetTouchByIndex(\n                    PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, i);\n                m << \"\\n    \" << tp.id() << \"//\"\n                  << tp.position().x() << \"/\" << tp.position().y()\n                  << \"@\" << tp.pressure();\n                if (tp.id() == touch_id_)\n                    has_tpid = true;\n            }\n\n            if (has_tpid) {\n                /* Emulate a click: only care about touch at id touch_id_ */\n                pp::TouchPoint tp = touch_event.GetTouchById(\n                    PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, touch_id_);\n\n                pp::Point touch_event_pos(\n                    tp.position().x() * scale_,\n                    tp.position().y() * scale_);\n                bool down = event.GetType() == PP_INPUTEVENT_TYPE_TOUCHSTART;\n\n                if (mouse_pos_.x() != touch_event_pos.x() ||\n                    mouse_pos_.y() != touch_event_pos.y()) {\n                    pending_mouse_move_ = true;\n                    mouse_pos_ = touch_event_pos;\n                }\n\n                m << \"\\nEmulated mouse \";\n\n                if (event.GetType() != PP_INPUTEVENT_TYPE_TOUCHMOVE) {\n                    m << (down ? \"DOWN\" : \"UP\");\n                    SendClick(1, down ? 1 : 0);\n                } else {\n                    m << \"MOVE\";\n                }\n\n                m << \" \" << touch_event_pos.x() << \"/\" << touch_event_pos.y();\n            }\n        } else if (event.GetType() == PP_INPUTEVENT_TYPE_IME_TEXT) {\n            /* FIXME: There are other IME event types... */\n            pp::IMEInputEvent ime_event(event);\n\n            /* FIXME: Do something with these events. We probably need to \"type\"\n             * the letters one by one... */\n\n            LogMessage(0) << \"IME TEXT: \" << ime_event.GetText().AsString();\n        }\n\n        return PP_TRUE;\n    }\n\nprivate:\n    /* Initializes Graphics context */\n    void InitContext() {\n        if (view_rect_.width() <= 0 || view_rect_.height() <= 0)\n            return;\n\n        scale_ = hidpi_ ? view_device_scale_ : 1.0f;\n        pp::Size new_size = pp::Size(view_rect_.width()  * scale_,\n        \t\t\t     view_rect_.height() * scale_);\n\n        LogMessage(0) << \"InitContext \"\n                      << new_size.width() << \"x\" << new_size.height()\n                      << \"s\" << scale_\n                      << \" (device scale: \" << view_device_scale_\n                      << \", zoom level: \" << view_css_scale_ << \")\";\n\n        const bool kIsAlwaysOpaque = true;\n        context_ = pp::Graphics2D(this, new_size, kIsAlwaysOpaque);\n        context_.SetScale(1.0f / scale_);\n        if (!BindGraphics(context_)) {\n            LogMessage(0) << \"Unable to bind 2d context!\";\n            context_ = pp::Graphics2D();\n            return;\n        }\n\n        size_ = new_size;\n        force_refresh_ = true;\n    }\n\n    /* Requests the server for a resolution change. */\n    void ChangeResolution(int width, int height) {\n        LogMessage(1) << \"Asked for resolution \" << width << \"x\" << height;\n\n        if (connected_) {\n            struct resolution* r;\n            pp::VarArrayBuffer array_buffer(sizeof(*r));\n            r = static_cast<struct resolution*>(array_buffer.Map());\n            r->type = 'R';\n            r->width = width;\n            r->height = height;\n            array_buffer.Unmap();\n            SocketSend(array_buffer, false);\n        } else {  /* Just assume we can take up the space */\n            ResizeMessage(width, height, scale_*view_css_scale_);\n        }\n    }\n\n    /* Converts \"IE\"/JavaScript keycode to X11 KeySym.\n     * See http://unixpapa.com/js/key.html\n     * TODO: Drop support for VF1 */\n    uint32_t KeyCodeToKeySym(uint32_t keycode, const std::string& code) {\n        if (keycode >= 65 && keycode <= 90)  /* A to Z */\n            return keycode + 32;\n        if (keycode >= 48 && keycode <= 57)  /* 0 to 9 */\n            return keycode;\n        if (keycode >= 96 && keycode <= 105)  /* KP 0 to 9 */\n            return keycode - 96 + 0xffb0;\n        if (keycode >= 112 && keycode <= 123)  /* F1-F12 */\n            return keycode - 112 + 0xffbe;\n\n        switch (keycode) {\n        case 8:   return 0xff08;  // backspace\n        case 9:   return 0xff09;  // tab\n        case 12:  return 0xff9d;  // num 5\n        case 13:  return 0xff0d;  // enter\n        case 16:  // shift\n            if (code == \"ShiftRight\") return 0xffe2;\n            return 0xffe1;  // (left)\n        case 17:  // control\n            if (code == \"ControlRight\") return 0xffe4;\n            return 0xffe3;  // (left)\n        case 18:  // alt\n            if (code == \"AltRight\") return 0xffea;\n            return 0xffe9;  // (left)\n        case 19:  return 0xff13;  // pause\n        case 20:  return 0;       // caps lock. FIXME: reenable (0xffe5)\n        case 27:  return 0xff1b;  // esc\n        case 32:  return 0x20;    // space\n        case 33:  return 0xff55;  // page up\n        case 34:  return 0xff56;  // page down\n        case 35:  return 0xff57;  // end\n        case 36:  return 0xff50;  // home\n        case 37:  return 0xff51;  // left\n        case 38:  return 0xff52;  // top\n        case 39:  return 0xff53;  // right\n        case 40:  return 0xff54;  // bottom\n        case 42:  return 0xff61;  // print screen\n        case 45:  return 0xff63;  // insert\n        case 46:  return 0xffff;  // delete\n        case 91:  return 0xffeb;  // super\n        case 106: return 0xffaa;  // num multiply\n        case 107: return 0xffab;  // num plus\n        case 109: return 0xffad;  // num minus\n        case 110: return 0xffae;  // num dot\n        case 111: return 0xffaf;  // num divide\n        case 144: return 0xff7f;  // num lock\n        case 145: return 0xff14;  // scroll lock\n        case 151: return 0x1008ff95;  // WLAN\n        case 166: return 0x1008ff26;  // back\n        case 167: return 0x1008ff27;  // forward\n        case 168: return 0x1008ff73;  // refresh\n        case 182: return 0x1008ff51;  // page flipper (\"F5\")\n        case 183: return 0x1008ff59;  // fullscreen/display\n        case 186: return 0x3b;  // ;\n        case 187: return 0x3d;  // =\n        case 188: return 0x2c;  // ,\n        case 189: return 0x2d;  // -\n        case 190: return 0x2e;  // .\n        case 191: return 0x2f;  // /\n        case 192: return 0x60;  // `\n        case 219: return 0x5b;  // [\n        case 220: return 0x5c;  // '\\'\n        case 221: return 0x5d;  // ]\n        case 222: return 0x27;  // '\n        case 229: return 0;  // dead key ('`~). FIXME: no way of knowing which\n        }\n\n        return 0x00;\n    }\n\n    /* Changes the target FPS: avoid unecessary refreshes to save CPU */\n    void SetTargetFPS(int new_target_fps) {\n        /* When increasing the fps, immediately ask for a frame, and force\n         * refresh the display (we probably just gained focus). */\n        if (new_target_fps > target_fps_) {\n            force_refresh_ = true;\n            RequestScreen(request_token_);\n        }\n        target_fps_ = new_target_fps;\n    }\n\n    /* Sends a mouse click.\n     * - button is a X11 button number (e.g. 1 is left click)\n     * SocketSend flushes the mouse position before the click is sent. */\n    void SendClick(int button, int down) {\n        struct mouseclick* mc;\n\n        if (!freon_) {\n            if (down && (search_state_ == kSearchUpFirst ||\n                        search_state_ == kSearchUp)) {\n                SendSearchKey(1);\n                search_state_ = kSearchDown;\n            }\n        }\n\n        pp::VarArrayBuffer array_buffer(sizeof(*mc));\n        mc = static_cast<struct mouseclick*>(array_buffer.Map());\n        mc->type = 'C';\n        mc->down = down;\n        mc->button = button;\n        array_buffer.Unmap();\n        SocketSend(array_buffer, true);\n\n        /* That means we have focus */\n        SetTargetFPS(kFullFPS);\n    }\n\n    void SendSearchKey(int down) {\n        /* TODO: Drop support for VF1 */\n        if (server_version_ == \"VF1\")\n            SendKeySym(0xffeb, down);\n        else\n            SendKeyCode(KeyCodeConverter::GetCode(\"OSLeft\", false), down);\n    }\n\n    /* Sends a keysym (VF1) */\n    /* TODO: Drop support for VF1 */\n    void SendKeySym(uint32_t keysym, int down) {\n        struct key_vf1* k;\n        pp::VarArrayBuffer array_buffer(sizeof(*k));\n        k = static_cast<struct key_vf1*>(array_buffer.Map());\n        k->type = 'K';\n        k->down = down;\n        k->keysym = keysym;\n        array_buffer.Unmap();\n        SocketSend(array_buffer, true);\n\n        /* That means we have focus */\n        SetTargetFPS(kFullFPS);\n    }\n\n    /* Sends a keycode */\n    void SendKeyCode(uint8_t keycode, int down) {\n        struct key* k;\n        pp::VarArrayBuffer array_buffer(sizeof(*k));\n        k = static_cast<struct key*>(array_buffer.Map());\n        k->type = 'K';\n        k->down = down;\n        k->keycode = keycode;\n        array_buffer.Unmap();\n        SocketSend(array_buffer, true);\n\n        /* That means we have focus */\n        SetTargetFPS(kFullFPS);\n    }\n\n    /* Requests the next framebuffer grab.\n     * The parameter is a token that must be equal to request_token_.\n     * This makes sure only one screen requests is waiting at one time\n     * (e.g. when changing frame rate), since we have no way of cancelling\n     * scheduled callbacks. */\n    void RequestScreen(int32_t token) {\n        LogMessage(3) << \"OnWaitEnd \" << token << \"/\" << request_token_;\n\n        if (!connected_) {\n            LogMessage(-1) << \"!connected\";\n            return;\n        }\n\n        /* Check that this request is up to date, and that no other\n         * request is flying */\n        if (token != request_token_  || screen_flying_) {\n            LogMessage(2) << \"Old token, or screen flying...\";\n            return;\n        }\n        screen_flying_ = true;\n        request_token_++;\n\n        struct screen* s;\n        pp::VarArrayBuffer array_buffer(sizeof(*s));\n        s = static_cast<struct screen*>(array_buffer.Map());\n\n        s->type = 'S';\n        s->shm = 1;\n        s->refresh = force_refresh_;\n        force_refresh_ = false;\n        s->width = image_data_.size().width();\n        s->height = image_data_.size().height();\n        s->paddr = (uint64_t)image_data_.data();\n        uint64_t sig = ((uint64_t)rand() << 32) ^ rand();\n        uint64_t* data = static_cast<uint64_t*>(image_data_.data());\n        *data = sig;\n        s->sig = sig;\n\n        array_buffer.Unmap();\n        SocketSend(array_buffer, true);\n    }\n\n    /* Called when the last frame was displayed (Vsync-ed): allocates next\n     * buffer and requests next frame.\n     * Parameter is ignored: used for callbacks */\n    void OnFlush(int32_t /*result*/ = 0) {\n        PP_Time time_ = pp::Module::Get()->core()->GetTime();\n        PP_Time deltat = time_-lasttime_;\n\n        double delay = (target_fps_>0) ? (1.0/target_fps_ - deltat) : INFINITY;\n\n        double cfps = deltat > 0 ? 1.0/deltat : 1000;\n        lasttime_ = time_;\n        k_++;\n\n        avgfps_ = 0.9*avgfps_ + 0.1*cfps;\n        if ((k_ % ((int)avgfps_+1)) == 0 || debug_ >= 1) {\n            LogMessage(0) << \"fps: \" << (int)(cfps+0.5)\n                          << \" (\" << (int)(avgfps_+0.5) << \")\"\n                          << \" delay: \" << (int)(delay*1000)\n                          << \" deltat: \" << (int)(deltat*1000)\n                          << \" target fps: \" << (int)(target_fps_)\n                          << \" \" << size_.width() << \"x\" << size_.height();\n        }\n\n        LogMessage(5) << \"OnFlush\";\n\n        screen_flying_ = false;\n\n        /* Allocate next image. If size_ is the same, the previous buffer will\n         * be reused. */\n        PP_ImageDataFormat format = pp::ImageData::GetNativeImageDataFormat();\n        image_data_ = pp::ImageData(this, format, size_, false);\n\n        /* Request for next frame */\n        if (isinf(delay)) {\n            return;\n        } else if (delay >= 0) {\n            pp::Module::Get()->core()->CallOnMainThread(\n                delay*1000,\n                callback_factory_.NewCallback(&KiwiInstance::RequestScreen),\n                request_token_);\n        } else {\n            RequestScreen(request_token_);\n        }\n    }\n\n    /* Paints the frame. In our context, simply replace the front buffer\n     * content with image_data_. */\n    void Paint(bool blank) {\n        if (context_.is_null()) {\n            /* The current Graphics2D context is null, so updating and rendering\n             * is pointless. */\n            flush_context_ = context_;\n            return;\n        }\n\n        if (blank) {\n            uint32_t* data = (uint32_t*)image_data_.data();\n            int size = image_data_.size().width()*image_data_.size().height();\n            for (int i = 0; i < size; i++) {\n                if (debug_ == 0)\n                    data[i] = 0xFF000000;\n                else\n                    data[i] = 0xFF800000 + i;\n            }\n        }\n\n        /* Using Graphics2D::ReplaceContents is the fastest way to update the\n         * entire canvas every frame. */\n        context_.ReplaceContents(&image_data_);\n\n        /* Store a reference to the context that is being flushed; this ensures\n         * the callback is called, even if context_ changes before the flush\n         * completes. */\n        flush_context_ = context_;\n        context_.Flush(\n            callback_factory_.NewCallback(&KiwiInstance::OnFlush));\n    }\n\nprivate:\n    /* Constants */\n    const int kFullFPS = 30;   /* Maximum fps */\n    const int kBlurFPS = 5;    /* fps when window is possibly hidden */\n    const int kHiddenFPS = 0;  /* fps when window is hidden */\n\n    const int kMaxRetry = 3;  /* Maximum number of connection attempts */\n\n    /* Class members */\n    pp::CompletionCallbackFactory<KiwiInstance> callback_factory_{this};\n    pp::Graphics2D context_;\n    pp::Graphics2D flush_context_;\n    pp::Rect view_rect_;\n    float view_device_scale_ = 1.0f;\n    float view_css_scale_ = 1.0f;\n    pp::Size size_;\n    float scale_ = 1.0f;\n\n    pp::ImageData image_data_;\n    int k_ = 0;\n\n    std::unique_ptr<pp::WebSocket> websocket_;\n    int retry_ = 0;\n    bool connected_ = false;\n    std::string server_version_ = \"\";\n    bool freon_ = false;\n    bool screen_flying_ = false;\n    pp::Var receive_var_;\n    int target_fps_ = kFullFPS;\n    int request_token_ = 0;\n    bool force_refresh_ = false;\n\n    bool pending_mouse_move_ = false;\n    pp::Point mouse_pos_{-1, -1};\n    /* Mouse wheel accumulators */\n    int mouse_wheel_x = 0;\n    int mouse_wheel_y = 0;\n\n    /* Search key state:\n     * - active/inactive: Key is pushed on Chromium OS side\n     * - down/up: Key is pushed on xiwi side */\n    enum {\n        kSearchInactive,  /* Inactive (up) */\n        kSearchUpFirst,   /* Active, up, no other key (yet) */\n        kSearchUp,        /* Active, up */\n        kSearchDown       /* Active, down */\n    } search_state_ = kSearchInactive;\n\n    /* Touch */\n    int touch_count_;  /* Number of points currently pressed */\n    int touch_id_;  /* First touch id */\n\n    /* Performance metrics */\n    PP_Time lasttime_;\n    double avgfps_ = 0.0;\n\n    /* Cursor cache */\n    class Cursor {\npublic:\n        pp::ImageData img;\n        pp::Point hot;\n    };\n    std::unordered_map<uint32_t, Cursor> cursor_cache_;\n\n    /* Display to connect to */\n    int display_ = -1;\n    int debug_ = 0;\n    bool hidpi_ = false;\n};\n\nclass KiwiModule : public pp::Module {\npublic:\n    KiwiModule() : pp::Module() {}\n    virtual ~KiwiModule() {}\n\n    virtual pp::Instance* CreateInstance(PP_Instance instance) {\n        return new KiwiInstance(instance);\n    }\n};\n\nnamespace pp {\n\nModule* CreateModule() {\n    return new KiwiModule();\n}\n\n}  /* namespace pp */\n"
  },
  {
    "path": "installer/.gitattributes",
    "content": "crouton text diff merge\n"
  },
  {
    "path": "installer/debian/defaults",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This file is sourced from main.sh to update distro-specific defaults.\n# It must set at least ARCH and MIRROR if not already specified.\n\nif [ -z \"$ARCH\" ]; then\n    ARCH=\"`uname -m`\"\n    if [ \"$RELEASE\" = 'wheezy' ] \\\n            && [ \"$ARCH\" = 'arm64' -o \"$ARCH\" = 'aarch64' ]; then\n        ARCH='armhf'\n    fi\nfi\n\ncase \"$ARCH\" in\nx86 | i?86) ARCH=\"i386\";;\nx86_64 | amd64) ARCH=\"amd64\";;\narmel) ARCH=\"armel\";;\narm64 | aarch64) ARCH=\"arm64\";;\narm*) ARCH=\"armhf\";;\n*) error 2 \"Invalid architecture '$ARCH'.\";;\nesac\n\nif [ -z \"$MIRROR\" ]; then\n    MIRROR=\"${CROUTON_MIRROR_debian:-http://deb.debian.org/debian/}\"\nfi\n\nif [ -z \"$MIRROR2\" ]; then\n    MIRROR2=\"${CROUTON_MIRROR2_debian:-http://security.debian.org/}\"\nfi\n\n"
  },
  {
    "path": "installer/debian/releases",
    "content": "potato!\nwoody!\nsarge!\netch!\nlenny!\nsqueeze!\nwheezy!\njessie!\nstretch!\nbuster!\nbullseye\nbookworm\ntrixie\nforky\nsid\n"
  },
  {
    "path": "installer/functions",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# This file contains functions common to many of the scripts.\n# The file is sourced when the scripts are run directly out of the repo, and\n# directly inserted into the files when built into a bundle.\n\n# Installs a script to a destination, statically inserting the below functions\n# in this file if this file is sourced. If the file is a symlink, copies the\n# symlink without modification.\n# $1: the source script to process\n# $2: the destination file or directory\n# $3: optional additional commands to pass to sed (passed to -e)\ninstallscript() {\n    local src=\"$1\" dst=\"$2\"\n    if [ -d \"$dst\" -o \"${dst%/}\" != \"$dst\" ]; then\n        mkdir -p \"$dst\"\n        dst=\"${dst%/}/${src##*/}\"\n    fi\n    if [ -h \"$src\" ]; then\n        cp -fPT \"$src\" \"$dst\"\n        return\n    fi\n    thisfile=\"${INSTALLERDIR:-\"`dirname \"$0\"`/../installer\"}/functions\"\n    if [ ! -f \"$thisfile\" ]; then\n        # This should never happen.\n        error 1 \"Unable to find common functions file.\"\n    fi\n    {\n        # Find the line sourcing the functions file\n        line=\"$(grep -n '^\\. .*/installer/functions' \"$src\" | cut -d : -f 1)\"\n        if [ -n \"$line\" ]; then\n            head -n $((line - 1)) \"$src\"\n            awk 'ok; !ok && /^###/ {ok=1}' \"$thisfile\"\n            tail -n +$((line + 1)) \"$src\"\n        else\n            cat \"$src\"\n        fi\n    } | sed -e \"${3:-;}\" > \"$dst\"\n}\n\n### Everything after this line will be statically inserted into scripts.\n\n# Exits the script with exit code $1, spitting out message $@ to stderr\nerror() {\n    local ecode=\"$1\"\n    shift\n    if [ \"$ecode\" -eq 1 ]; then\n        echo_color \"r\" \"$*\" 1>&2\n    else\n        echo \"$*\" 1>&2\n    fi\n    exit \"$ecode\"\n}\n\n# Setup trap ($1) in case of interrupt or error.\n# Traps explicitly do not exit on command error.\n# Traps are first disabled to avoid executing clean-up commands twice.\n# In the case of interrupts, exit is called to avoid the script continuing.\n# $1 must either be empty or end in a semicolon.\nsettrap() {\n    trap \"set +e; trap - INT HUP TERM 0; $1 exit 2\" INT HUP TERM\n    trap \"set +e; trap - INT HUP TERM 0; $1\" 0\n}\n\n# Prepend a command to the existing $TRAP\naddtrap() {\n    OLDTRAP=\"$TRAP\"\n    TRAP=\"$1;$TRAP\"\n    settrap \"$TRAP\"\n}\n\n# Revert the last trap change\nundotrap() {\n    TRAP=\"$OLDTRAP\"\n    settrap \"$TRAP\"\n}\n\n# Works mostly like built-in getopts but silently coalesces positional arguments.\n# Does not take parameters. Set getopts_string prior to calling.\n# Sets getopts_var and getopts_arg.\n# $@ will be left with the positional arguments, so you should NOT shift at all.\n# In bash, enables alias expansion, but that shouldn't impact anything.\nshopt -q -s expand_aliases 2>/dev/null || true\n# Running count of the number of positional arguments\n# We're done processing if all of the remaining arguments are positional.\ngetopts_npos=0\ngetopts_dashdash=''\nalias getopts_nextarg='getopts_ret=1\n    while [ \"$#\" -gt \"$getopts_npos\" ]; do\n        if [ -z \"$getopts_dashdash\" ] && getopts \"$getopts_string\" getopts_var; then\n            if [ \"$(($#+1-OPTIND))\" -lt \"$getopts_npos\" ]; then\n                # Bad parameter usage ate a positional argument.\n                # Generate the proper error message by abusing getopts.\n                set -- \"-$getopts_var\"\n                getopts \"$getopts_var:\" getopts_var\n                shift\n            fi\n            getopts_arg=\"$OPTARG\"\n            getopts_ret=0\n            # Avoid -- confusion by shifting if OPTARG is set\n            if [ -n \"$OPTARG\" ]; then\n                shift \"$((OPTIND-1))\"\n                OPTIND=1\n            fi\n            break\n        fi\n        # Do not let getopts consume a --\n        if [ \"$OPTIND\" -gt 1 ]; then\n            shift \"$((OPTIND-2))\"\n            if [ \"$1\" != \"--\" ]; then\n                shift\n            fi\n        fi\n        OPTIND=1\n        if [ -z \"$getopts_dashdash\" -a \"$1\" = \"--\" ]; then\n            # Still need to loop through to fix the ordering\n            getopts_dashdash=y\n        else\n            set -- \"$@\" \"$1\"\n            getopts_npos=\"$((getopts_npos+1))\"\n        fi\n        shift\n    done\n    [ \"$getopts_ret\" = 0 ]'\n\n# Compares $RELEASE to the specified releases, assuming $DISTRODIR/releases is\n# sorted oldest to newest. Every two parameters are considered criteria that are\n# ORed together. The first parameter is the comparator, as provided to \"test\".\n# The second parameter is the release to compare to. A comparison against a\n# release from a different distro always fails. Since either $DISTRODIR/releases\n# has to be readable or the release list has to be embedded, and RELEASE has to\n# be set properly, this function should only be used in the context of targets.\n# Returns non-zero if the release doesn't match\n# Example:  release -ge quantal -ge wheezy\nrelease() {\n    if [ \"$(($# % 2))\" -ne 0 ]; then\n        error 3 \"$(echo_color \"y\" \"invalid parameters to release(): $*\")\"\n    fi\n    # Load up the list of releases; this will be replaced with a literal list\n    local releases=\"`cat \"$DISTRODIR/releases\" 2>/dev/null`\"\n    if [ -z \"$releases\" ]; then\n        error 3 \"$(echo_color \"y\" \"list of releases for $DISTRO not found\")\"\n    fi\n    # End-of-word regex for awk\n    local eow='([^a-z]|$)'\n    local relnum=\"`echo \"$releases\" | awk \"/^$RELEASE$eow/ {print NR; exit}\"`\"\n    if [ -z \"$relnum\" ]; then\n        error 3 \"$(echo_color \"y\" \"$RELEASE not found in $DISTRO\")\"\n    fi\n    while [ \"$#\" -ge 2 ]; do\n        local cmp=\"`echo \"$releases\" | awk \"/^$2$eow/ {print NR; exit}\"`\"\n        if [ -n \"$cmp\" ] && test \"$relnum\" \"$1\" \"$cmp\"; then\n            return 0\n        fi\n        shift 2\n    done\n    return 1\n}\n\n# Large writes to slow devices (e.g. SD card or USB stick) can cause a task to\n# be stuck for longer than 120 seconds, which triggers a kernel panic (and an\n# immediate reboot). Instead of disabling the timeout altogether, we just make\n# sure the kernel does not panic (this is the default configuration of a vanilla\n# kernel). See crbug.com/260955 for details.\ndisablehungtask() {\n    echo 0 > /proc/sys/kernel/hung_task_panic\n}\n\n# Run an awk program, without buffering its output.\n# unbuffered_awk 'program' [argument ...]\n# In the awk code, all \"print\" calls must be replaced by \"output\"\n#\n# - Detects whether to run mawk or gawk (mawk is preferred as it is faster),\n# - Prepends the definition of the \"output\" function to the awk program\n# - Run the modified awk program.\n# mawk needs '-W interactive' to flush the output properly (fflush does not\n# work as documented, but apparently this behaviour is intentional, see\n# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=593504).\n# Furthermore, fflush is not POSIX so we cannot use any other awk flavor.\nunbuffered_awk() {\n    local cmd\n    if hash mawk 2>/dev/null; then\n        cmd=\"mawk -W interactive\"\n    elif hash gawk 2>/dev/null; then\n        cmd=\"gawk\"\n    else\n        echo \"Cannot find mawk or gawk.\" 1>&2\n        return 1\n    fi\n\n    local program=\"$1\"\n    shift\n\n    $cmd '\n    function output(s) {\n        print s\n        fflush()\n    }\n    '\"$program\" \"$@\"\n}\n\n# Validate a chroot name: It cannot contain any /, and cannot be \".\", \"..\" nor\n# an empty string.\nvalidate_name() {\n    if [ \"${1%/*}\" != \"$1\" -o \"$1\" = \".\" -o \"$1\" = \"..\" -o -z \"$1\" ]; then\n        return 1\n    fi\n    return 0\n}\n\n# Returns the mountpoint a path is on. The path doesn't need to exist.\n# $1: the path to check\n# outputs on stdout\ngetmountpoint() {\n    mp=\"`readlink -m -- \"$1\"`\"\n    while ! stat -c '%m' \"${mp:-/}\" 2>/dev/null; do\n        mp=\"${mp%/*}\"\n    done\n}\n\n# Echos to stderr, or /dev/tty if stdin is a tty but stderr is not\necho_tty() {\n    if [ -t 0 -a ! -t 2 ]; then\n        echo \"$@\" 1>/dev/tty\n    else\n        echo \"$@\" 1>&2\n    fi\n}\n\n# Outputs colored text to stdout\n# usage: echo_color [l][color] [colored string] [uncolored string]\n# Specifying \"t\" (thick) uses bold text.\n# Color can be red, green, yellow, blue, magenta, cyan.\n# Color can be specified with just the first letter.\n# example: echo_color \"tr\" \"bold red\" \"normal text\"\necho_color() {\n    # If not outputting to a tty print no colors.\n    if [ ! -t 2 ]; then\n        shift\n        echo \"$@\"\n        return\n    fi\n    printf \"\\033[\"\n    local c=\"$1\"\n    if [ \"${c#t}\" != \"$c\" ]; then\n        printf \"1;\"\n        c=\"${c#t}\"\n    else\n        printf \"0;\"\n    fi\n    case \"$c\" in\n        r*) printf \"31m\";;\n        g*) printf \"32m\";;\n        y*) printf \"33m\";;\n        b*) printf \"34m\";;\n        m*) printf \"35m\";;\n        c*) printf \"36m\";;\n         *) printf \"37m\";;\n    esac\n    shift\n    local s='\\n'\n    if [ \"$1\" = '-n' ]; then\n        s=''\n        shift\n    fi\n    echo '-n' \"$1\"\n    printf \"\\033[0m\"\n    shift\n    echo '-n' \"$*\"\n    printf \"$s\"\n}\n\n# Websocket interface\nPIPEDIR='/tmp/crouton-ext'\nCRIATDISPLAY=\"$PIPEDIR/kiwi-display\"\nCROUTONLOCKDIR='/tmp/crouton-lock'\n\n# Write a command to croutonwebsocket, and read back response\nwebsocketcommand() {\n    # Check that $PIPEDIR and the FIFO pipes exist\n    if ! [ -d \"$PIPEDIR\" -a -p \"$PIPEDIR/in\" -a -p \"$PIPEDIR/out\" ]; then\n        echo \"EError $PIPEDIR/in or $PIPEDIR/out are not pipes.\"\n        exit 0\n    fi\n\n    if ! timeout 3 \\\n           sh -c \"flock 5; cat > '$PIPEDIR/in';\n                  cat '$PIPEDIR/out'\" 5>\"$PIPEDIR/lock\"; then\n        echo \"EError timeout\"\n    fi\n    chmod -Rf g+rwX \"$PIPEDIR\" || true\n    chgrp -Rf crouton \"$PIPEDIR\" || true\n}\n"
  },
  {
    "path": "installer/kali/defaults",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This file is sourced from main.sh to update distro-specific defaults.\n# It must set at least ARCH and MIRROR if not already specified.\n\nif [ -z \"$ARCH\" ]; then\n    ARCH=\"`uname -m`\"\nfi\n\ncase \"$ARCH\" in\nx86 | i?86) ARCH=\"i386\";;\nx86_64 | amd64) ARCH=\"amd64\";;\narmel) ARCH=\"armel\";;\narm64 | aarch64) ARCH=\"arm64\";;\narm*) ARCH=\"armhf\";;\n*) error 2 \"Invalid architecture '$ARCH'.\";;\nesac\n\nif [ -z \"$MIRROR\" ]; then\n    MIRROR=\"${CROUTON_MIRROR_kali:-http://http.kali.org}\"\nfi\n\nif [ -z \"$MIRROR2\" ]; then\n    MIRROR2=\"${CROUTON_MIRROR2_kali:-http://security.kali.org/kali-security}\"\nfi\n\nBOOTSTRAP_RELEASE=\"stable\"\n\n\n"
  },
  {
    "path": "installer/kali/releases",
    "content": "moto!\nkali!\nsana!\nkali-rolling\n"
  },
  {
    "path": "installer/main.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nSCRIPTDIR=\"${SCRIPTDIR:-\"`dirname \"$0\"`/..\"}\"\nCHROOTBINDIR=\"$SCRIPTDIR/chroot-bin\"\nCHROOTETCDIR=\"$SCRIPTDIR/chroot-etc\"\nDISTRODIR=''\nINSTALLERDIR=\"$SCRIPTDIR/installer\"\nHOSTBINDIR=\"$SCRIPTDIR/host-bin\"\nTARGETSDIR=\"$SCRIPTDIR/targets\"\nSRCDIR=\"$SCRIPTDIR/src\"\n\nARCH=''\nBOOTSTRAP_RELEASE=''\nDISTRO=''\nDOWNLOADONLY=''\nENCRYPT=''\nKEYFILE=''\nMIRROR=''\nMIRRORSET=''\nMIRROR2=''\nNAME=''\nPREFIX='/usr/local'\nPREFIXSET=''\nCHROOTSLINK='/mnt/stateful_partition/crouton/chroots'\nPROXY='unspecified'\nRELEASE=''\nRESTORE=''\nRESTOREBIN=''\nDEFAULTRELEASE='xenial'\nPREVIOUS_DEFAULT_RELEASES='precise'\nTARBALL=''\nTARGETS=''\nTARGETFILE=''\nUPDATE=''\nUPDATEIGNOREEXISTING=''\n\nUSAGE=\"$APPLICATION [options] -t targets\n$APPLICATION [options] -f backup_tarball\n$APPLICATION [options] -d -f bootstrap_tarball\n\nConstructs a chroot for running a more standard userspace alongside Chromium OS.\n\nIf run with -f, where the tarball is a backup previously made using edit-chroot,\nthe chroot is restored and relevant scripts installed.\n\nIf run with -d, a bootstrap tarball is created to speed up chroot creation in\nthe future. You can use bootstrap tarballs generated this way by passing them\nto -f the next time you create a chroot with the same architecture and release.\n\n$APPLICATION must be run as root unless -d is specified AND fakeroot is\ninstalled AND /tmp is mounted exec and dev.\n\nIt is highly recommended to run this from a crosh shell (Ctrl+Alt+T), not VT2.\n\nOptions:\n    -a ARCH     The architecture to prepare a new chroot or bootstrap for.\n                Default: autodetected for the current chroot or system.\n    -b          Restore crouton scripts in PREFIX/bin, as required by the\n                chroots currently installed in PREFIX/chroots.\n    -d          Downloads the bootstrap tarball but does not prepare the chroot.\n    -e          Encrypt the chroot with ecryptfs using a passphrase.\n                If specified twice, prompt to change the encryption passphrase.\n    -f TARBALL  The bootstrap or backup tarball to use, or to download to (-d).\n                When using an existing tarball, -a and -r are ignored.\n    -k KEYFILE  File or directory to store the (encrypted) encryption keys in.\n                If unspecified, the keys will be stored in the chroot if doing a\n                first encryption, or auto-detected on existing chroots.\n    -m MIRROR   Mirror to use for bootstrapping and package installation.\n                Default depends on the release chosen.\n                Can only be specified during chroot creation and forced updates\n                (-u -u). After installation, the mirror can be modified using\n                the distribution's recommended way.\n    -M MIRROR2  A secondary mirror, often used for security updates.\n                Can only be specified alongside -m.\n    -n NAME     Name of the chroot. Default is the release name.\n                Cannot contain any slash (/).\n    -p PREFIX   The root directory in which to install the bin and chroot\n                subdirectories and data.\n                Default: $PREFIX, with $PREFIX/chroots linked to\n                $CHROOTSLINK.\n    -P PROXY    Set an HTTP proxy for the chroot; effectively sets http_proxy.\n                Specify an empty string to remove a proxy when updating.\n    -r RELEASE  Name of the distribution release. Default: $DEFAULTRELEASE,\n                or auto-detected if upgrading a chroot and -n is specified.\n                Specify 'help' or 'list' to print out recognized releases.\n    -t TARGETS  Comma-separated list of environment targets to install.\n                Specify 'help' or 'list' to print out potential targets.\n    -T TARGETFILE  Path to a custom target definition file that gets applied to\n                the chroot as if it were a target in the $APPLICATION bundle.\n    -u          If the chroot exists, runs the preparation step again.\n                You can use this to install new targets or update old ones.\n                Passing this parameter twice will force an update even if the\n                specified release does not match the one already installed.\n    -V          Prints the version of the installer to stdout.\n\nBe aware that dev mode is inherently insecure, even if you have a strong\npassword in your chroot! Anyone can simply switch VTs and gain root access\nunless you've permanently assigned a Chromium OS root password. Encrypted\nchroots require you to set a Chromium OS root password, but are still only as\nsecure as the passphrases you assign to them.\"\n# \"Undocumented\" flags:\n#   -U          Same as -u, but does not reinstall existing targets.\n#               Targets specified with -t will be installed, but not recorded\n#               for future updates.\n\n# Common functions\n. \"$SCRIPTDIR/installer/functions\"\n\n# Process arguments\nwhile getopts 'a:bdef:k:m:M:n:p:P:r:s:t:T:uUV' f; do\n    case \"$f\" in\n    a) ARCH=\"$OPTARG\";;\n    b) RESTOREBIN='y';;\n    d) DOWNLOADONLY='y';;\n    e) ENCRYPT=\"${ENCRYPT:-\"-\"}e\";;\n    f) TARBALL=\"$OPTARG\";;\n    k) KEYFILE=\"$OPTARG\";;\n    m) MIRROR=\"$OPTARG\"; MIRRORSET='y';;\n    M) MIRROR2=\"$OPTARG\";;\n    n) NAME=\"$OPTARG\";;\n    p) PREFIX=\"`readlink -m -- \"$OPTARG\"`\"; PREFIXSET='y';;\n    P) PROXY=\"$OPTARG\";;\n    r) RELEASE=\"$OPTARG\";;\n    t) TARGETS=\"$TARGETS${TARGETS:+\",\"}$OPTARG\";;\n    T) TARGETFILE=\"$OPTARG\";;\n    u) UPDATE=\"$((UPDATE+1))\";;\n    U) UPDATE=\"$((UPDATE+1))\"; UPDATEIGNOREEXISTING='y';;\n    V) echo \"$APPLICATION: version ${VERSION:-\"git\"}\"; exit 0;;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\nshift \"$((OPTIND-1))\"\n\n# Check against the minimum version of Chromium OS\nif [ -n \"$DOWNLOADONLY\" ]; then\n    :\nelif ! grep -q '_RELEASE_BUILD_NUMBER=' /etc/lsb-release 2>/dev/null; then\n    error 2 \"$APPLICATION can only be installed in the Chromium OS dev mode shell.\"\nelif ! awk -F= '/_RELEASE_BUILD_NUMBER=/ { exit int($2) < '\"${CROS_MIN_VERS:-0}\"' }' \\\n        '/etc/lsb-release' 2>/dev/null; then\n    error 2 \"Your version of Chromium OS is extraordinarily old.\nIf there are updates pending, please reboot and try again.\nOtherwise, you may not be getting automatic updates, in which case you should\npost your update_engine.log from chrome://system to http://crbug.com/new and\nrestore your device using a recovery USB:\n  https://support.google.com/chromebook/answer/1080595\"\nfi\n\n# If the release is \"list\" or \"help\", print out all the valid releases.\nif [ \"$RELEASE\" = 'list' -o \"$RELEASE\" = 'help' ]; then\n    for DISTRODIR in \"$INSTALLERDIR\"/*/; do\n        DISTRODIR=\"${DISTRODIR%/}\"\n        DISTRO=\"${DISTRODIR##*/}\"\n        echo \"Recognized $DISTRO releases:\" 1>&2\n        accum=''\n        while IFS=\"|\" read -r RELEASE _; do\n            newaccum=\"${accum:-\"   \"} $RELEASE\"\n            if [ \"${#newaccum}\" -gt 80 ]; then\n                echo \"$accum\" 1>&2\n                newaccum=\"    $RELEASE\"\n            fi\n            accum=\"$newaccum\"\n        done < \"$DISTRODIR/releases\"\n        if [ -n \"$accum\" ]; then\n            echo \"$accum\" 1>&2\n        fi\n    done\n    echo 'Releases marked with ! are upstream end-of-life, and should be avoided.' 1>&2\n    echo 'Releases marked with * are unsupported, but may work with some effort.' 1>&2\n    exit 2\nfi\n\n# Either a tarball, update, target, or restore binaries must be specified.\nif [ -z \"$TARBALL$UPDATE$TARGETS$TARGETFILE$RESTOREBIN\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Only one of 'download only', update and restore binaries can be specified\ntest=\"$DOWNLOADONLY${UPDATE:+y}$RESTOREBIN\"\nif [ \"${#test}\" -gt 1 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# ARCH cannot be specified upon update\nif [ -n \"$UPDATE$RESTOREBIN\" -a -n \"$ARCH\" ]; then\n    error 2 'Architecture cannot be specified with -b or -u.'\nfi\n\n# Release or name cannot be specified when restoring binaries\nif [ -n \"$RESTOREBIN\" -a -n \"$NAME$RELEASE$ENCRYPT\" ]; then\n    error 2 \"Name, release and encrypt cannot be specified with -b.\"\nfi\n\n# MIRROR and MIRROR2 must not be specified on update\nif [ \"$UPDATE\" = 1 -o -n \"$RESTOREBIN\" ]; then\n    if [ -z \"$MIRROR$MIRROR2\" ]; then\n        # Makes sure MIRROR does not get overriden by distribution default\n        MIRROR='unspecified'\n    else\n        error 2 \"$USAGE\"\n    fi\nfi\n\n# Prefix must exist\nif [ ! -d \"$PREFIX\" ]; then\n    error 2 \"$PREFIX is not a valid prefix\"\nfi\n\n# There should never be any extra parameters.\nif [ $# != 0 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# If this script was called with '-x' or '-v', pass that to prepare.sh\nSETOPTIONS=\"\"\nif set -o | grep -q '^xtrace.*on$'; then\n    SETOPTIONS=\"-x\"\nfi\nif set -o | grep -q '^verbose.*on$'; then\n    SETOPTIONS=\"$SETOPTIONS -v\"\nfi\nsh() {\n    /bin/sh $SETOPTIONS -e \"$@\"\n}\n\nif [ \"$USER\" = root -o \"$UID\" = 0 ]; then\n    # Avoid kernel panics due to slow I/O when restoring or bootstrapping\n    disablehungtask\nfi\n\n# If we specified a tarball, we need to detect the tarball type\nif [ -z \"$DOWNLOADONLY\" -a -n \"$TARBALL\" ]; then\n    if [ ! -f \"$TARBALL\" ]; then\n        error 2 \"$TARBALL not found.\"\n    fi\n    label=\"`tar --test-label -f \"$TARBALL\" 2>/dev/null`\"\n    if [ -n \"$label\" ]; then\n        if [ \"${label#crouton:backup}\" != \"$label\" ]; then\n            releasearch=''\n            if [ -z \"$NAME\" ]; then\n                NAME=\"${label#*-}\"\n            fi\n        elif [ \"${label#crouton:bootstrap}\" != \"$label\" ]; then\n            releasearch=\"${label#*.}\"\n        else\n            error 2 \"$TARBALL doesn't appear to be a valid crouton bootstrap.\"\n        fi\n    else\n        # Old bootstraps just use the first folder name\n        echo \"WARNING: $TARBALL is an old-style bootstrap or backup.\" 1>&2\n        releasearch=\"`tar -tf \"$TARBALL\" 2>/dev/null | head -n 1`\"\n        releasearch=\"${releasearch%%/*}\"\n    fi\n    if [ \"${releasearch#*-}\" != \"$releasearch\" ]; then\n        ARCH=\"${releasearch##*-}\"\n        RELEASE=\"${releasearch%-*}\"\n    else\n        RESTORE='y'\n        if [ -z \"$NAME\" ]; then\n            NAME=\"$releasearch\"\n        fi\n    fi\nelif [ -n \"$DOWNLOADONLY\" -a -s \"$TARBALL\" ]; then\n    error 2 \"$TARBALL already exists; refusing to overwrite it!\"\nfi\n\n# If we're not restoring, updating, or bootstrapping, targets must be specified\nif [ -z \"$RESTORE$RESTOREBIN$UPDATE$DOWNLOADONLY$TARGETS$TARGETFILE\" ]; then\n    error 2 \"$USAGE\"\nfi\n\nif [ -n \"$RESTORE\" -a -n \"$TARGETS$TARGETFILE\" -a -z \"$UPDATE\" ]; then\n    error 2 \"Specify -u if you want to add targets when you restore a chroot.\"\nfi\n\n# Detect which distro the release belongs to.\nif [ -n \"$RELEASE\" -o -z \"$UPDATE\" ]; then\n    if [ -z \"$RELEASE\" ]; then\n        RELEASE=\"$DEFAULTRELEASE\"\n    fi\n    for DISTRODIR in \"$INSTALLERDIR\"/*/; do\n        DISTRODIR=\"${DISTRODIR%/}\"\n        if grep -q \"^$RELEASE\\([^a-z].*\\)*$\" \"$DISTRODIR/releases\"; then\n            DISTRO=\"${DISTRODIR##*/}\"\n            . \"$DISTRODIR/defaults\"\n            break\n        fi\n    done\n    if [ -z \"$DISTRO\" ]; then\n        error 2 \"$RELEASE does not belong to any supported distribution.\"\n    fi\nfi\n\n# Confirm or list targets if requested (and download only isn't chosen)\nif [ -z \"$DOWNLOADONLY\" ]; then\n    t=\"${TARGETS%,},\"\n    while [ -n \"$t\" ]; do\n        TARGET=\"${t%%,*}\"\n        t=\"${t#*,}\"\n        if [ -z \"$TARGET\" ]; then\n            continue\n        elif [ \"$TARGET\" = 'help' -o \"$TARGET\" = 'list' ]; then\n            TARGETS='help'\n            echo \"Available targets:\" 1>&2\n            for t in \"$TARGETSDIR/\"*; do\n                TARGET=\"${t##*/}\"\n                if [ \"${TARGET%common}\" = \"$TARGET\" ]; then\n                    (TARGETNOINSTALL='y'; . \"$t\") 1>&2\n                fi\n            done\n            exit 2\n        elif [ \"${TARGET%common}\" != \"$TARGET\" ] || \\\n             [ ! -r \"$TARGETSDIR/$TARGET\" ] || \\\n             ! (TARGETNOINSTALL=\"${UPDATE:-c}\"; . \"$TARGETSDIR/$TARGET\"); then\n            error 2 \"Invalid target \\\"$TARGET\\\".\"\n        fi\n    done\n    if [ -n \"$TARGETFILE\" ]; then\n        if [ ! -r \"$TARGETFILE\" ]; then\n            error 2 \"Could not find \\\"$TARGETFILE\\\".\"\n        elif [ ! -f \"$TARGETFILE\" ]; then\n            error 2 \"\\\"$TARGETFILE\\\" is not a target definition file.\"\n        fi\n    fi\nfi\n\n# If we're not running as root, we must be downloading and have fakeroot and\n# have an exec and dev /tmp\nif grep -q '.* /tmp .*\\(nodev\\|noexec\\|nosymfollow\\)' /proc/mounts; then\n    NOEXECTMP=y\nelse\n    NOEXECTMP=n\nfi\nFAKEROOT=''\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    FAKEROOT=fakeroot\n    if [ \"$NOEXECTMP\" = y -o -z \"$DOWNLOADONLY\" ] \\\n            || ! hash \"$FAKEROOT\" 2>/dev/null; then\n        error 2 \"$APPLICATION must be run as root.\"\n    fi\nfi\n\n# If we are only downloading, we need a destination tarball\nif [ -n \"$DOWNLOADONLY\" -a -z \"$TARBALL\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Check if we're running from a tty, which does not interact well with X11\nif [ -z \"$DOWNLOADONLY\" ] && \\\n        readlink -f -- \"/proc/$$/fd/0\" | grep -q '^/dev/tty'; then\n    echo \\\n\"WARNING: It is highly recommended that you run $APPLICATION from a crosh shell\n(Ctrl+Alt+T in Chromium OS), not from a VT. If you continue to run this from a\nVT, you're gonna have a bad time. Press Ctrl-C at any point to abort.\" 1>&2\n    sleep 5\nfi\n\n# Set http_proxy if a proxy is specified.\nif [ \"$PROXY\" != 'unspecified' ]; then\n    export http_proxy=\"$PROXY\" https_proxy=\"$PROXY\" ftp_proxy=\"$PROXY\"\nfi\n\n# Done with parameter processing!\n# Make sure we always have echo when this script exits\naddtrap \"stty echo 2>/dev/null\"\n\n# Determine directories\nBIN=\"$PREFIX/bin\"\nCHROOTS=\"$PREFIX/chroots\"\n\nif [ -z \"$RESTOREBIN\" ]; then\n    # Fix NAME if it was not specified.\n    # If updating and name/release weren't specified, check previous defaults\n    if [ -n \"$UPDATE\" -a -z \"$NAME$RELEASE\" ]; then\n        for rel in \"$DEFAULTRELEASE\" $PREVIOUS_DEFAULT_RELEASES; do\n            if [ -d \"$CHROOTS/$rel\" ]; then\n                DEFAULTRELEASE=\"$rel\"\n                break\n            fi\n        done\n    fi\n    CHROOT=\"$CHROOTS/${NAME:=\"${RELEASE:-\"$DEFAULTRELEASE\"}\"}\"\n    CHROOTSRC=\"$CHROOT\"\nfi\nTARGETDEDUPFILE=\"`mktemp --tmpdir=/tmp \"$APPLICATION-dedup.XXX\"`\"\naddtrap \"rm -f '$TARGETDEDUPFILE'\"\n\n# Confirm we have write access to the directory before starting.\nif [ -z \"$RESTOREBIN$DOWNLOADONLY\" ]; then\n    # Validate chroot name\n    if ! validate_name \"$NAME\"; then\n        error 2 \"Invalid chroot name '$NAME'.\"\n    fi\n\n    # If no prefix is set, check that /usr/local/chroots ($CHROOTS) is a\n    # symbolic link to /mnt/stateful_partition/crouton/chroots ($CHROOTSLINK)\n    # /mnt/stateful_partition/dev_image is bind-mounted to /usr/local, so mv\n    # does not understand that they are on the same filesystem\n    # Instead, use the direct path, and confirm that they're actually the same\n    # to catch situations where things are bind-mounted over /usr/local\n    truechroots=\"/mnt/stateful_partition/dev_image/chroots\"\n    if [ -z \"$PREFIXSET\" -a ! -h \"$CHROOTS\" ] \\\n            && ([ ! -e \"$CHROOTS\" ] || [ \"$CHROOTS\" -ef \"$truechroots\" ]); then\n        # Detect if chroots are left in the old chroots directory, and move them\n        # to the new directory.\n        if [ -e \"$CHROOTS\" ] && ! rmdir \"$CHROOTS\" 2>/dev/null; then\n            echo \\\n\"Migrating data from legacy chroots directory $CHROOTS to $CHROOTSLINK...\" 1>&2\n\n            # Check that CHROOTSLINK is empty\n            if [ -e \"$CHROOTSLINK\" ] && ! rmdir \"$CHROOTSLINK\" 2>/dev/null; then\n                error 1 \\\n\"There is data in both $CHROOTS and $CHROOTSLINK.\nMake sure all chroots are unmounted, then manually move the contents of\n$truechroots to $CHROOTSLINK.\"\n            fi\n\n            # Wait for currently-mounted chroots to be unmounted\n            if grep -q \"$CHROOTS\" /proc/mounts && \\\n                    ! sh \"$HOSTBINDIR/unmount-chroot\" -a -y -c \"$CHROOTS\"; then\n                echo -n \\\n\"The above chroots appear to be running from the legacy chroots directory.\nLog out of all running chroots and the install will automatically continue.\nPress Ctrl-C at any time to abort the installation.\" 1>&2\n                while grep -q \"$CHROOTS\" /proc/mounts; do\n                    sleep 1\n                done\n                echo 1>&2\n            fi\n\n            mkdir -p \"$CHROOTSLINK\"\n            mv -T \"$truechroots\" \"$CHROOTSLINK\"\n        fi\n        ln -sT \"$CHROOTSLINK\" \"$CHROOTS\"\n    fi\n\n    create='-n'\n    if [ -d \"$CHROOT\" ] && ! rmdir \"$CHROOT\" 2>/dev/null; then\n        if [ -n \"$RESTORE\" ]; then\n            error 1 \"$CHROOTSRC already has stuff in it!\nEither delete it, specify a different name (-n), or use edit-chroot to restore.\"\n        elif [ -z \"$UPDATE\" ]; then\n            error 1 \"$CHROOTSRC already has stuff in it!\nEither delete it, specify a different name (-n), or specify -u to update it.\"\n        fi\n        create=''\n        echo \"$CHROOTSRC already exists; updating it...\" 1>&2\n    elif [ -n \"$UPDATE\" -a -z \"$RESTORE\" ]; then\n        error 1 \"$CHROOTSRC does not exist; cannot update.\nValid chroots:\n`sh \"$HOSTBINDIR/edit-chroot\" -c \"$CHROOTS\" -a`\"\n    fi\n\n    # Chroot must be located on an ext filesystem\n    if df -T \"`getmountpoint \"$CHROOT\"`\" | awk '$2~\"^ext\"{exit 1}'; then\n        error 1 \"$CHROOTSRC is not an ext filesystem.\"\n    fi\n\n    # Restore the chroot now\n    if [ -n \"$RESTORE\" ]; then\n        sh \"$HOSTBINDIR/edit-chroot\" -r -f \"$TARBALL\" -c \"$CHROOTS\" -- \"$NAME\"\n    fi\n\n    # Mount the chroot and update CHROOT path\n    CHROOT=\"`sh \"$HOSTBINDIR/mount-chroot\" -k \"$KEYFILE\" \\\n             $create $ENCRYPT -p -c \"$CHROOTS\" -- \"$NAME\"`\"\n\n    # Remove the directory if bootstrapping fails. Also delete if the only file\n    # there is .ecryptfs (valid chroots have far more than 1 file)\n    addtrap \"[ \\\"\\`ls -a '$CHROOTS/$NAME' 2>/dev/null | wc -l\\`\\\" -le 3 ] \\\n                && rm -rf '$CHROOTS/$NAME'\"\n\n    # Auto-unmount the chroot when the script exits\n    addtrap \"sh '$HOSTBINDIR/unmount-chroot' -y -c '$CHROOTS' -- '$NAME' 2>/dev/null\"\n\n    # Sanity-check the release if we're updating\n    if [ -n \"$UPDATE\" -a -n \"$RELEASE\" ] &&\n            [ \"`sh \"$DISTRODIR/getrelease.sh\" -r \"$CHROOT\"`\" != \"$RELEASE\" ]; then\n        if [ \"$UPDATE\" != 2 ]; then\n            error 1 \\\n\"Release doesn't match! Please correct the -r option, or specify a second -u to\nchange the release, upgrading the chroot (dangerous).\"\n        else\n            echo \"WARNING: Changing the chroot release to $RELEASE.\" 1>&2\n            echo \"Press Control-C to abort; upgrade will continue in 5 seconds.\" 1>&2\n            sleep 5\n        fi\n    elif [ -n \"$UPDATE\" -a -z \"$RELEASE\" ]; then\n        # Detect the release\n        for DISTRODIR in \"$INSTALLERDIR\"/*/; do\n            DISTRODIR=\"${DISTRODIR%/}\"\n            if RELEASE=\"`sh \"$DISTRODIR/getrelease.sh\" -r \"$CHROOT\"`\"; then\n                DISTRO=\"${DISTRODIR##*/}\"\n                . \"$DISTRODIR/defaults\"\n                break\n            fi\n        done\n        if [ -z \"$DISTRO\" ]; then\n            error 2 \"Unable to determine the release in $CHROOTSRC. Please specify it with -r.\"\n        fi\n    fi\n\n    # Enforce the correct architecture\n    if [ -n \"$UPDATE\" ]; then\n        ARCH=\"`sh \"$DISTRODIR/getrelease.sh\" -a \"$CHROOT\"`\"\n    fi\n\n    mkdir -p \"$BIN\"\nfi\n\n# Check if RELEASE is supported\nreleaseline=\"`sed -n \"s/^\\($RELEASE[^a-z|]*\\)\\(|.*\\)*$/\\1/p\" \\\n                                                         \"$DISTRODIR/releases\"`\"\nif [ \"${releaseline%\"!\"}\" != \"$releaseline\" ]; then\n    echo_color tr \"WARNING: $RELEASE has reached upstream end-of-life.\"\n\n    if [ -z \"$UPDATE\" ]; then\n        if [ -z \"$MIRRORSET\" ]; then\n            error 2 \"\\\nThat means there will be no package updates available.\nYou also have to specify a mirror to crouton (-m) for installation to proceed.\"\n        fi\n        echo \"\\\nYou have specified a mirror, so installation will proceed anyway.\nYou will almost certainly run into issues, but some features may still work.\nPress Ctrl-C to abort; installation will continue in 5 seconds.\" 1>&2\n    else\n        echo \"\\\nThat means you may have issues updating now or in the future.\nYou should upgrade your chroot to a supported version as soon as possible.\nRefer to the wiki for upgrade instructions:\n  https://github.com/dnschneid/crouton/wiki/Upgrade-chroot-release\nPress Ctrl-C to abort; normal update will continue in 5 seconds.\" 1>&2\n    fi\n    sleep 5\nelif [ \"${releaseline%\"*\"}\" != \"$releaseline\" ]; then\n    echo_color r \"WARNING: $RELEASE is an unsupported release.\" \"\nYou will likely run into issues, but things may work with some effort.\" 1>&2\nfi\n\n# Checks if it's safe to enable boot signing verification.\n# We check by attempting to mount / read-write. We do so in a bind mount to a\n# temporary directory to avoid changing its state permanently if it is\n# successful.\nvboot_is_safe() {\n    local tmp=\"`mktemp -d --tmpdir=/tmp 'crouton-rwtest.XXX'`\"\n    local unmount=\"umount -l '$tmp' 2>/dev/null; rmdir '$tmp'\"\n    addtrap \"$unmount\"\n    mount --bind / \"$tmp\" >/dev/null\n    local ret=1\n    mount -o remount,rw \"$tmp\" 2>/dev/null || ret=0\n    undotrap\n    eval \"$unmount\"\n    return \"$ret\"\n}\n\n# Check and update dev boot settings. This may fail on old systems; ignore it.\naltfw=dev_boot_altfw\nif crossystem \"$altfw\" 2>&1 >&- | grep -q Invalid; then\n    altfw=dev_boot_legacy\nfi\nif [ -z \"$DOWNLOADONLY\" ] && ! vboot_is_safe; then\n    echo \"WARNING: Your rootfs is writable. Signed boot verification cannot be enabled.\" 1>&2\n    echo \"If this is a surprise to you, you should do a full system recovery via USB.\" 1>&2\n    sleep 5\nelif [ -z \"$DOWNLOADONLY\" ] && \\\n    boot=\"`crossystem dev_boot_usb $altfw dev_boot_signed_only`\"; then\n    # db_usb and db_legacy be off, db_signed_only should be on.\n    echo \"$boot\" | {\n        read -r usb legacy signed\n        suggest=''\n        if [ \"$usb\" != 0 ]; then\n            echo \"WARNING: USB booting is enabled; consider disabling it.\" 1>&2\n            suggest=\"$suggest dev_boot_usb=0\"\n        fi\n        if [ \"$legacy\" != 0 ]; then\n            echo \"WARNING: Legacy booting is enabled; consider disabling it.\" 1>&2\n            suggest=\"$suggest ${altfw}=0\"\n        fi\n        if [ -n \"$suggest\" ]; then\n            if [ \"$signed\" != 1 ]; then\n                echo \"WARNING: Signed boot verification is disabled; consider enabling it.\" 1>&2\n                suggest=\"$suggest dev_boot_signed_only=1\"\n            fi\n            echo \"You can use the following command: sudo crossystem$suggest\" 1>&2\n            sleep 5\n        elif [ \"$signed\" != 1 ]; then\n            # Only enable signed booting if the user hasn't enabled alternate\n            # boot options, since those are opt-in.\n            echo \"WARNING: Signed boot verification is disabled; enabling it for security.\" 1>&2\n            echo \"You can disable it again using: sudo crossystem dev_boot_signed_only=0\" 1>&2\n            crossystem dev_boot_signed_only=1 || true\n            sleep 2\n        fi\n    }\nfi\n\n# Unpack the tarball if appropriate\nif [ -z \"$RESTOREBIN$RESTORE$UPDATE$DOWNLOADONLY\" ]; then\n    echo \"Installing $RELEASE-$ARCH chroot to $CHROOTSRC\" 1>&2\n    if [ -n \"$TARBALL\" ]; then\n        # Unpack the chroot\n        echo 'Unpacking chroot environment...' 1>&2\n        tar -C \"$CHROOT\" --strip-components=1 -xf \"$TARBALL\"\n    fi\nelif [ -z \"$RESTOREBIN$RESTORE$UPDATE\" ]; then\n    echo \"Downloading $RELEASE-$ARCH bootstrap to $TARBALL\" 1>&2\nfi\n\n# Download the bootstrap data if appropriate\nif [ -z \"$UPDATE$RESTOREBIN\" ] && [ -n \"$DOWNLOADONLY\" -o -z \"$TARBALL\" ]; then\n    # Create the temporary directory and delete it upon exit\n    tmp=\"`mktemp -d --tmpdir=/tmp \"$APPLICATION.XXX\"`\"\n    subdir=\"$RELEASE-$ARCH\"\n    addtrap \"rm -rf --one-file-system '$tmp'\"\n\n    # Ensure that the temporary directory has exec+dev, or mount a new ramfs\n    if [ \"$NOEXECTMP\" = 'y' ]; then\n        mount -i -t ramfs -o 'rw,dev,exec' ramfs \"$tmp\"\n        # Ensure symfollow is set on platforms that feature it, but don't fail\n        # on ones that do not.\n        mount -o 'remount,symfollow' \"$tmp\" 2>/dev/null || true\n        addtrap \"umount -l '$tmp'\"\n    fi\n\n    . \"$DISTRODIR/bootstrap\"\n\n    # Tar it up if we're only downloading\n    if [ -n \"$DOWNLOADONLY\" ]; then\n        echo 'Compressing bootstrap files...' 1>&2\n        $FAKEROOT tar -C \"$tmp\" -V \"crouton:bootstrap.$subdir\" \\\n                      -cajf \"$TARBALL\" \"$subdir\"\n        echo 'Done!' 1>&2\n        exit 0\n    fi\n\n    # Move it to the right place\n    echo 'Moving bootstrap files into the chroot...' 1>&2\n    # Make sure we do not leave an incomplete chroot in case of interrupt or\n    # error during the move\n    addtrap \"rm -rf --one-file-system '$CHROOT'\"\n    mv -f \"$tmp/$subdir/\"* \"$CHROOT\"\n    undotrap\nfi\n\n# Add the list of targets in file $1 to $TARGETS\ndeduptargets() {\n    if [ -r \"$1\" ]; then\n        t=\"`tr '\\n' , < \"$1\"`\"\n        t=\"${t%,},\"\n        while [ -n \"$t\" ]; do\n            TARGET=\"${t%%,*}\"\n            t=\"${t#*,}\"\n            if [ -z \"$TARGET\" ]; then\n                continue\n            fi\n            # Don't put duplicate entries in the targets list\n            tlist=\",$TARGETS,\"\n            if [ \"${tlist%\",$TARGET,\"*}\" != \"$tlist\" ]; then\n                continue\n            fi\n            if [ ! -r \"$TARGETSDIR/$TARGET\" ]; then\n                echo \"Previously installed target '$TARGET' no longer exists.\" 1>&2\n                continue\n            fi\n            # Add the target\n            TARGETS=\"${TARGETS%,},$TARGET\"\n        done\n    fi\n}\n\nif [ -z \"$RESTOREBIN\" ] && [ -z \"$RESTORE\" -o -n \"$UPDATE\" ]; then\n    PREPARE=\"$CHROOT/prepare.sh\"\n\n    # Create the setup script inside the chroot\n    echo 'Preparing chroot environment...' 1>&2\n    VAREXPAND=\"s/releases=.*\\$/releases=\\\"\\\n`sed 's/$/\\\\\\\\/' \"$DISTRODIR/releases\"`\n\\\"/;\"\n    VAREXPAND=\"${VAREXPAND}s #ARCH# $ARCH ;s #DISTRO# $DISTRO ;\"\n    VAREXPAND=\"${VAREXPAND}s #MIRROR# $MIRROR ;s #MIRROR2# $MIRROR2 ;\"\n    VAREXPAND=\"${VAREXPAND}s #RELEASE# $RELEASE ;s #PROXY# $PROXY ;\"\n    VAREXPAND=\"${VAREXPAND}s #VERSION# ${VERSION:-\"git\"} ;\"\n    VAREXPAND=\"${VAREXPAND}s #USERNAME# $CROUTON_USERNAME ;\"\n    VAREXPAND=\"${VAREXPAND}s/#SETOPTIONS#/$SETOPTIONS/;\"\n    installscript \"$INSTALLERDIR/prepare.sh\" \"$PREPARE\" \"$VAREXPAND\"\n    # Append the distro-specific prepare.sh\n    cat \"$DISTRODIR/prepare\" >> \"$PREPARE\"\nelse # Restore host-bin only\n    PREPARE=\"/dev/null\"\n\n    # Make sure targets are aware that we only want to restore host-bin\n    RESTOREHOSTBIN='y'\nfi\n\nif [ -z \"$RESTOREBIN\" ]; then\n    # Ensure that /usr/local/bin and /etc/crouton exist\n    mkdir -p \"$CHROOT/usr/local/bin\" \"$CHROOT/etc/crouton\"\n\n    # If -U was not specified, update existing targets.\n    if [ -z \"$UPDATEIGNOREEXISTING\" ]; then\n        TARGETSFILE=\"$CHROOT/etc/crouton/targets\"\n\n        # Read the explicit targets file in the chroot\n        deduptargets \"$TARGETSFILE\"\n\n        if [ -z \"$TARGETS\" ]; then\n            error 1 \"\\\nNo target list found (your chroot may be very old).\nPlease specify targets with -t.\"\n        fi\n\n        # Reset the installed target list files\n        echo \"$TARGETS\" > \"$TARGETSFILE\"\n    fi\nelse\n    # Collect targets over all chroots (ignore the ones that cannot be mounted)\n    for chroot in \"$CHROOTS\"/*; do\n        deduptargets \"$chroot/.crouton-targets\"\n    done\nfi\n\necho -n '' > \"$TARGETDEDUPFILE\"\n# Check if a target has defined PROVIDES, if we are not restoring host-bin.\nif [ ! -n \"$RESTOREHOSTBIN\" ]; then\n    # Create temporary file to list PROVIDES=TARGET.\n    PROVIDESFILE=\"`mktemp --tmpdir=/tmp \"$APPLICATION-provides.XXX\"`\"\n    addtrap \"rm -f '$PROVIDESFILE'\"\n    t=\"${TARGETS%,},\"\n    while [ -n \"$t\" ]; do\n        TARGET=\"${t%%,*}\"\n        t=\"${t#*,}\"\n        if [ -n \"$TARGET\" ]; then\n            (TARGETNOINSTALL=\"p\"; . \"$TARGETSDIR/$TARGET\")\n        fi\n    done\nfi\n\n# Run each target, appending stdout to the prepare script.\nunset SIMULATE\nTARGETNOINSTALL=\"$RESTOREHOSTBIN\"\nif [ -n \"$TARGETFILE\" ]; then\n    TARGET=\"`readlink -f -- \"$TARGETFILE\"`\"\n    (. \"$TARGET\") >> \"$PREPARE\"\nfi\nt=\"${TARGETS%,},post-common,\"\nwhile [ -n \"$t\" ]; do\n    TARGET=\"${t%%,*}\"\n    t=\"${t#*,}\"\n    if [ -n \"$TARGET\" ]; then\n        (. \"$TARGETSDIR/$TARGET\") >> \"$PREPARE\"\n    fi\ndone\n\nif [ -f \"$PREPARE\" ]; then\n    # Update .crouton-targets in the unencrypted part of the chroot\n    cp -fT \"$TARGETDEDUPFILE\" \"$CHROOTSRC/.crouton-targets\"\n\n    chmod 500 \"$PREPARE\"\n\n    # Run the setup script inside the chroot\n    sh -e \"$HOSTBINDIR/enter-chroot\" -c \"$CHROOTS\" -n \"$NAME\" -xx\nfi\n\necho \"Done! You can enter the chroot using enter-chroot.\" 1>&2\n"
  },
  {
    "path": "installer/prepare.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nARCH='#ARCH#'\nMIRROR='#MIRROR#'\nMIRROR2='#MIRROR2#'\nDISTRO='#DISTRO#'\nRELEASE='#RELEASE#'\nPROXY='#PROXY#'\nVERSION='#VERSION#'\nUSERNAME='#USERNAME#'\nSETOPTIONS='#SETOPTIONS#'\n\n# Additional set options: -x or -v can be added for debugging (-e is always on)\nif [ -n \"$SETOPTIONS\" ]; then\n    set $SETOPTIONS\nfi\n\n# We need all paths to do administrative things\nexport PATH='/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'\n\n# Common functions\n. \"`dirname \"$0\"`/../installer/functions\"\n\n# Takes in a list of crouton-style package names, and outputs the list, filtered\n# for the current distro.\n# A crouton-style package name looks something like the following:\n#   distro_a+distro_b=pkg_for_a_b,distro_c=pkg_for_c,pkg_for_others\n# That means distros A and B will use the first package name, distro C will use\n# the second, and all others will use the third. You can also specify a specific\n# release in a distro via: distro~release to filter to a specific release.\n# If distros have DISTROAKA set (see the distro's prepare script), they can also\n# use the pkg name for a distro they are derived from if one specific for them\n# has not been specified. So if the distro is \"ubuntu\", it will use a \"debian\"\n# entry if one is specified and an \"ubuntu\" one is not.\n# The list is not ordered; instead there's a priority to the filtering:\n#   specific release > distro > parent distro > any distro\n# It's safe for a package name to have an = in it as long as the distro is\n# specified. This is good for specifying a specific version of a Debian package\n# to install, for instance.\n# Leaving a pkg name blank will result in that package being dropped for that\n# distro. You can do so for all distros not listed by leaving a trailing comma.\n# If no package for the distro is found, this will spit out an error and exit.\ndistropkgs() {\n    local descriptor desc pkgname rank option optionname optiondistro optionrel\n    # For each package\n    for descriptor in \"$@\"; do\n        pkgname='.'\n        rank=0\n        # For each filter/pkgname pair (add a comma to consider trailing commas)\n        desc=\"$descriptor,\"\n        while [ -n \"$desc\" ]; do\n            option=\"${desc%%,*}\"\n            optionname=\"${option#*=}\"\n            desc=\"${desc#*,}\"\n            if [ \"$optionname\" = \"$option\" ]; then\n                # No filter specified; minimum rank.\n                if [ \"$rank\" -lt 1 ]; then\n                    rank=1\n                    pkgname=\"$optionname\"\n                fi\n                continue\n            fi\n            # For each distro option in the filter\n            option=\"${option%%=*}\"\n            option=\"${option%+}+\"\n            while [ -n \"$option\" ]; do\n                optiondistro=\"${option%%+*}\"\n                option=\"${option#*+}\"\n                if [ \"${optiondistro%~*}\" = \"$DISTRO\" ]; then\n                    optionrel=\"${optiondistro#*~}\"\n                    if [ \"$optionrel\" = \"$optiondistro\" ]; then\n                        # No specific release specified\n                        if [ \"$rank\" -lt 3 ]; then\n                            rank=3\n                            pkgname=\"$optionname\"\n                        fi\n                    elif [ \"$optionrel\" = \"$RELEASE\" ]; then\n                        # Specific release matches\n                        if [ \"$rank\" -lt 4 ]; then\n                            rank=4\n                            pkgname=\"$optionname\"\n                        fi\n                    fi\n                elif [ \"$optiondistro\" = \"$DISTROAKA\" ]; then\n                    # Option matches parent distro\n                    if [ \"$rank\" -lt 2 ]; then\n                        rank=2\n                        pkgname=\"$optionname\"\n                    fi\n                fi\n            done\n        done\n        # Print out the result or error out if nothing found\n        if [ \"$pkgname\" != '.' ]; then\n            echo -n \"$pkgname \"\n        else\n            error 2 \"Nothing specified for $DISTRO~$RELEASE in '$descriptor'\"\n        fi\n    done\n}\n\n\n# install [--minimal] [--asdeps] <packages> -- <avoid packages>\n# For the specified crouton-style package names (see distropkgs()), installs\n# <packages>, while avoiding installing <avoid packages> if they are not\n# already installed.\n# If --minimal is specified, avoids installing unnecessary packages.\n# Unlike --minimal, <avoid packages> is useful for allowing \"recommended\"\n# dependencies to be brought in while avoiding installing specific ones.\n# Distros without \"recommended\" dependencies can ignore <avoid packages>.\n# --asdeps installs the packages, but marks them as dependencies if they are not\n# already installed. Running the distro-equivalent of 'autoremove' at the end\n# of his script will uninstall such packages, as they are considered as orphans.\ninstall() {\n    install_dist `distropkgs \"$@\"`\n}\n\n\n# install_pkg: Installs the specified package file(s), ignoring dependency\n# problems, and then attempts to resolve the dependencies. If called without\n# parameters, simply fixes any dependency problems.\n# If the first parameter is --minimal, avoids installing unnecessary packages.\n# Distros that do not support external packages can implement this as an error.\ninstall_pkg() {\n    install_pkg_dist \"$@\"\n}\n\n\n# install_dummy: Installs a dummy package that resolves dependencies. The\n# parameters are a list of crouton-style package names to \"install\", optionally\n# followed by -- and the packages it depends on.\ninstall_dummy() {\n    install_dummy_dist `distropkgs \"$@\"`\n}\n\n\n# remove: Removes the specified packages. See distropkgs() for package syntax.\nremove() {\n    remove_dist `distropkgs \"$@\"`\n}\n\n\n# list_uninstalled: For the specified crouton-style package names (see\n# distropkgs()), prints out any package that is not already installed.\n# Appends each package printed out with the contents of the first parameter.\nlist_uninstalled() {\n    suffix=\"$1\"\n    shift\n    list_uninstalled_dist \"$suffix\" `distropkgs \"$@\"`\n}\n\n\n# Requests a re-launch of the preparation script with a fresh chroot setup.\n# Generally called immediately after bootstrapping to fix the environment.\nrelaunch_setup() {\n    exit 0\n}\n\n\n# Fixes the tty keyboard mode. keyboard-configuration puts tty1~6 in UTF8 mode,\n# assuming they are consoles. This isn't true for Chromium OS and crouton, and\n# X11 sessions need to be in RAW mode. We do the smart thing and revert ttys\n# with X sessions back to RAW.  keyboard-configuration could be reconfigured\n# after bootstrap, dpkg --configure -a, or dist-upgrade.\nfixkeyboardmode() {\n    if hash kbd_mode 2>/dev/null; then\n        for tty in `ps -CX -CXorg -otname=`; do\n            # On some systems, the tty of Chromium OS returns ?\n            if [ \"$tty\" = \"?\" ]; then\n                tty='tty1'\n            fi\n            if [ -e \"/dev/$tty\" ]; then\n                kbd_mode -s -C \"/dev/$tty\" 2>/dev/null || true\n            fi\n        done\n    fi\n}\n\n\n# compile: Grabs the necessary dependencies and then compiles a C file from\n# stdin to the specified output and strips it. Finally, removes whatever it\n# installed. This allows targets to provide on-demand binaries without\n# increasing the size of the chroot after install.\n# $1: name; target is /usr/local/(bin|lib)/crouton$1[.so]\n# $2: linker flags, quoted together\n# [$3]: if 'so', compiles as a shared object and installs it into lib\n# $3+: any package dependencies other than gcc and libc-dev, crouton-style.\ncompile() {\n    local out=\"/usr/local/bin/crouton$1\"\n    local linker=\"$2\"\n    local cflags='-xc -Os -I/usr/src/crouton'\n    if [ \"$3\" = 'so' ]; then\n        out=\"/usr/local/lib/crouton$1.so\"\n        cflags=\"$cflags -shared -fPIC\"\n        shift 1\n    fi\n    shift 2\n    echo \"Installing dependencies for $out...\" 1>&2\n    local pkgs=\"gcc libc6-dev $*\"\n    install --minimal --asdeps $pkgs </dev/null\n    echo \"Compiling $out...\" 1>&2\n    local tmp=\"`mktemp crouton.XXXXXX --tmpdir=/tmp`\"\n    addtrap \"rm -f '$tmp'\"\n    gcc $cflags - $linker -o \"$tmp\"\n    /usr/bin/install -sDT \"$tmp\" \"$out\"\n}\n\n\n# Convert an automake Makefile.am into a shell script, and provide useful\n# functions to compile libraries and executables.\n# Needs to be run in the same directory as the Makefile.am file.\n# This outputs the converted Makefile.am to stdout, which is meant to be\n# piped to sh -s (see audio and xiat for examples)\nconvert_automake() {\n    echo '\n        top_srcdir=\"..\"\n        top_builddir=\"..\"\n    '\n    sed -e '\n        # Concatenate lines ending in \\\n        : start; /\\\\$/{N; b start}\n        s/ *\\\\\\n[ \\t]*/ /g\n        # Convert automake to shell\n        s/^[^ ]*:/#\\0/\n        s/^\\t/#\\0/\n        s/\\t/ /g\n        s/ *= */=/\n        s/\\([^ ]*\\) *+= */\\1=${\\1}\\ /\n        s/ /\\\\ /g\n        y/()/{}/\n        s/if\\\\ !\\(.*\\)/if [ -z \"${\\1}\" ]; then/\n        s/if\\\\ \\([^!].*\\)/if [ -n \"${\\1}\" ]; then/\n        s/endif/fi/\n    ' 'Makefile.am'\n    echo '\n        # buildsources: Build all source files for target\n        #  $1: target\n        #  $2: additional gcc flags\n        # Prints a list of .o files\n        buildsources() {\n            local target=\"$1\"\n            local extragccflags=\"$2\"\n\n            eval local sources=\\\"\\$${target}_SOURCES\\\"\n            eval local cppflags=\\\"\\$${target}_CPPFLAGS\\\"\n            local cflags=\"$cppflags ${CFLAGS} ${AM_CFLAGS}\"\n\n            for dep in $sources; do\n                if [ \"${dep%.c}\" != \"$dep\" ]; then\n                    ofile=\"${dep%.c}.o\"\n                    gcc -c \"$dep\" -o \"$ofile\" '\"$archgccflags\"' \\\n                        $cflags $extragccflags 1>&2 || return $?\n                    echo -n \"$ofile \"\n                fi\n            done\n        }\n\n        # fixlibadd:\n        # Fix list of libraries ($1): replace lib<x>.la by -l<x>\n        fixlibadd() {\n            for libdep in $*; do\n                if [ \"${libdep%.la}\" != \"$libdep\" ]; then\n                    libdep=\"${libdep%.la}\"\n                    libdep=\"-l${libdep#lib}\"\n                fi\n                echo -n \"$libdep \"\n            done\n        }\n\n        # buildlib: Build a library\n        #  $1: library name\n        #  $2: additional linker flags\n        buildlib() {\n            local lib=\"$1\"\n            local extraflags=\"$2\"\n            local ofiles\n            # local eats the return status: separate the 2 statements\n            ofiles=\"`buildsources \"${lib}_la\" \"-fPIC -DPIC\"`\"\n\n            eval local libadd=\\\"\\$${lib}_la_LIBADD\\\"\n            eval local ldflags=\\\"\\$${lib}_la_LDFLAGS\\\"\n\n            libadd=\"`fixlibadd $libadd`\"\n\n            # Detect library version (e.g. 0.0.0)\n            local fullver=\"`echo -n \"$ldflags\" | \\\n                      sed -n '\\''y/:/./; \\\n                                 s/.*-version-info \\([0-9.]*\\)$/\\\\1/p'\\''`\"\n            local shortver=\"\"\n            # Get \"short\" library version (e.g. 0)\n            if [ -n \"$fullver\" ]; then\n                shortver=\".${fullver%%.*}\"\n                fullver=\".$fullver\"\n            fi\n            local fullso=\"$lib.so$fullver\"\n            local shortso=\"$lib.so$shortver\"\n            gcc -shared -fPIC -DPIC $ofiles $libadd -o \"$fullso\" \\\n                '\"$archgccflags\"' $extraflags -Wl,-soname,\"$shortso\"\n            if [ -n \"$fullver\" ]; then\n                ln -sf \"$fullso\" \"$shortso\"\n                # Needed at link-time only\n                ln -sf \"$shortso\" \"$lib.so\"\n            fi\n        }\n\n        # buildexe: Build an executable file\n        #  $1: executable file name\n        #  $2: additional linker flags\n        buildexe() {\n            local exe=\"$1\"\n            local extraflags=\"$2\"\n            local ofiles=\"`buildsources \"$exe\" \"\"`\"\n\n            eval local ldadd=\\\"\\$${exe}_LDADD\\\"\n            eval local ldflags=\\\"\\$${exe}_LDFLAGS\\\"\n\n            ldadd=\"`fixlibadd $ldadd`\"\n\n            gcc $ofiles $ldadd -o \"$exe\" '\"$archgccflags\"' $extraflags\n        }\n    '\n}\n\n\n# The rest is dictated first by the distro-specific prepare.sh, and then by the\n# selected targets.\n\n# All distro-specific prepare.sh scripts must define the install_dist,\n# install_pkg_dist, remove_dist, and list_uninstalled_dist functions.\n# They must also set PKGEXT and DISTROAKA.\n# Finally, they must do whatever distro-specific bootstrapping is necessary.\n\n# Note that we install targets before adding the user, since targets may affect\n# /etc/skel or other default parts. The user is added in post-common, which is\n# always added to targets.\n"
  },
  {
    "path": "installer/ubuntu/ar",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Processes and extracts AR files so that the ar binary is not needed.\n# Same interface as the real ar, but only supports -p, -t, and -x.\n\nAPPLICATION=\"${0##*/}\"\n\n# First parameter is the operation\ncase \"$1\" in\n    -p|p) OPERATION=p;;\n    -t|t) OPERATION=t;;\n    -x|x) OPERATION=x;;\n    *) OPERATION='';;\nesac\nif [ -z \"$OPERATION\" -o \"$#\" -lt 2 ]; then\n    echo \"USAGE: $APPLICATION -p|-t|-x archive [member...]\" 1>&2\n    exit 1\nfi\n\n# Second is the archive\nARCHIVE=\"$2\"\nif [ ! -f \"$ARCHIVE\" ]; then\n    echo \"$APPLICATION: $ARCHIVE: No such file\" 1>&2\n    exit 9\nelif [ ! -r \"$ARCHIVE\" ]; then\n    echo \"$APPLICATION: $ARCHIVE: Permission denied\" 1>&2\n    exit 1\nfi\n\n# Remaining (optional) are specific members of the archive\nshift 2\n\n# Read in from $ARCHIVE\n{\n    # Check header\n    read -r magic\n    if [ \"$magic\" != \"!<arch>\" ]; then\n        echo \"$APPLICATION: $ARCHIVE: File format not recognized\" 1>&2\n        exit 1\n    fi\n\n    # Process each file\n    while read -r name modtime owner group mode size magic; do\n        # Absorb extra newlines\n        if [ -z \"$name\" ]; then\n            continue;\n        fi\n        # Confirm magic\n        if [ \"$magic\" != '`' ]; then\n            echo \"$APPLICATION: $ARCHIVE: Malformed archive\" 1>&2\n            exit 1\n        fi\n        # Filter members\n        if [ $# != 0 ]; then\n            found='n'\n            for member in \"$@\"; do\n                if [ \"$name\" = \"$member\" ]; then\n                    found='y'\n                    break\n                fi\n            done\n            if [ \"$found\" = 'n' ]; then\n                # Consume file and skip\n                head -c \"$size\" > /dev/null\n                continue\n            fi\n        fi\n        # Do the requested action\n        case \"$OPERATION\" in\n            t) echo \"$name\"; head -c \"$size\" > /dev/null;;\n            p) head -c \"$size\";;\n            x) head -c \"$size\" > \"$name\";;\n        esac\n    done\n} < \"$ARCHIVE\"\n"
  },
  {
    "path": "installer/ubuntu/bootstrap",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This is a distro-specific bootstrap script, sourced from main.sh, and as such\n# has access to all of the variables set by main.sh, namely $tmp (the temporary\n# directory), $INSTALLERDIR/$DISTRO, $RELEASE, $BOOTSTRAP_RELEASE (if different\n# from $RELEASE), $ARCH, and $MIRROR.\n\n# This script is also sourced with nothing but $tmp set, as part of the bundle\n# build process. Any pre-downloading of tools should happen and be stored in\n# $tmp; the contents of which will be moved into $INSTALLERDIR/.. in the bundle\n\n# Grab debootstrap\nd=\"https://salsa.debian.org/installer-team/debootstrap/-/archive/1.0.140_bpo12+1/.tar.gz\"\n\n# Download and patch if it's not bundled already\nif [ -n \"${INSTALLERDIR-}\" -a -d \"$INSTALLERDIR/../debootstrap\" ]; then\n    cp -at \"$tmp\" \"$INSTALLERDIR/../debootstrap/\"*\nelse\n    # Add the subdirectory if we're preparing the installer bundle\n    if [ -z \"$RELEASE\" ]; then\n        tmp=\"$tmp/debootstrap\"\n        mkdir -p \"$tmp\"\n    fi\n\n    if ! curl -f -# -L --connect-timeout 60 --retry 2 \"$d\" \\\n            | tar -C \"$tmp\" --strip-components=1 --exclude=debian -zx 2>&-; then\n        error 1 'Failed to download debootstrap.\nCheck your internet connection or proxy settings and try again.'\n    fi\n\n    # Patch debootstrap so that it retries downloading packages\n    echo 'Patching debootstrap...' 1>&2\n    if awk '\n        t == 4 && /-z \"\\$checksum\"/ { sub(/\\$checksum/, \"$checksum$failed\"); t=5 }\n        t == 3 && /\"\\$checksum\" != \"\"/ { sub(/ \\];/, \" -a -z \\\"$failed\\\" ];\"); t=4 }\n        t == 2 && /if ! just_get \"\\$from\" \"\\$dest2\"; then continue 2; fi/ {\n            sub(/continue 2; fi/, \"failed=y; fi\"); t=3 }\n        t == 1 && /info RETRIEVING/ { print \"failed=\\\"\\\"\"; t=2 }\n        /\"\\$iters\" -lt 10/ { sub(/10/, \"3\"); t=1 }\n        1\n        END { if (t != 5) exit 1 }\n            ' \"$tmp/functions\" > \"$tmp/functions.new\"; then\n        mv -f \"$tmp/functions.new\" \"$tmp/functions\"\n    else\n        rm -f \"$tmp/functions.new\"\n        echo \"Unable to patch debootstrap, moving on...\" 1>&2\n    fi\n\n    # Patch debootstrap so that is does not create devices under /dev (issue #2387).\n    sed -i -e 's/^setup_devices () {$/\\0 return 0/' \"$tmp/functions\"\n\n    # Fix incorrect quoting in wgetprogress call (d45ca044136553)\n    sed -i -e 's/wgetprogress \"$CHECKCERTIF\" \"$CERTIFICATE\" \"$PRIVATEKEY\"'\\\n'/wgetprogress $CHECKCERTIF $CERTIFICATE $PRIVATEKEY/' \"$tmp/functions\"\n\n    # Patch debootstrap to use curl instead of wget\n    # Note that we do not translate other parameter, and lose the progress bar, but\n    # we do not use these anyway.\n    # FIXME: include curl wrapper script instead?\n    sed -i -e 's/wgetprogress\\(.*\\) -O \"$dest\"/curl\\1 -f -L -o \"$dest\"/' \"$tmp/functions\"\n    sed -i -e 's/in_path wget/in_path curl/' \"$tmp/debootstrap\"\n\n    # debootstrap wants a file to initialize /dev with, but we don't actually\n    # want any files there. Create an empty tarball that it can extract.\n    tar -czf \"$tmp/devices.tar.gz\" -T /dev/null\nfi\n\nif [ -n \"$RELEASE\" ]; then\n    # There is no bootstrap script for some distros derived from Debian. Thus we use\n    # the scripts for matching upstream distros to bootstrap the derived distros.\n    if [ ! -f \"$tmp/scripts/$RELEASE\" ]; then\n        ln -s \"$tmp/scripts/$BOOTSTRAP_RELEASE\" \"$tmp/scripts/$RELEASE\"\n    fi\n\n    # bionic/buster onwards does not require installing apt-transport-https (apt\n    # provides it). In theory, there is a transitional package, but debootstrap\n    # does not find it on bionic. Somewhat relevant bug:\n    #   https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=879755\n    #\n    # TODO: Drop this when debootstrap handles this case properly\n    if ! release -le artful -le stretch -le kali; then\n        sed -e 's/ apt-transport-https / /' -i \"$tmp/scripts/$RELEASE\"\n    fi\n\n    # Add the necessary debootstrap executables\n    newpath=\"$PATH:$tmp\"\n    cp \"$INSTALLERDIR/$DISTRO/ar\" \"$INSTALLERDIR/$DISTRO/pkgdetails\" \"$tmp/\"\n    chmod 755 \"$tmp/ar\" \"$tmp/pkgdetails\"\n\n    # Grab the release and drop it into the subdirectory\n    echo 'Downloading bootstrap files...' 1>&2\n    if ! PATH=\"$newpath\" DEBOOTSTRAP_DIR=\"$tmp\" $FAKEROOT \\\n            \"$tmp/debootstrap\" --foreign --extractor='ar' --arch=\"$ARCH\" \\\n            \"$RELEASE\" \"$tmp/$subdir\" \"$MIRROR\" 1>&2; then\n        echo \"debootstrap error log:\" 1>&2\n        tail -n 3 \"$tmp/$subdir/debootstrap/debootstrap.log\" 1>&2 || true\n        error 1 'Failed to run debootstrap.'\n    fi\nfi\n"
  },
  {
    "path": "installer/ubuntu/defaults",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This file is sourced from main.sh to update distro-specific defaults.\n# It must set at least ARCH and MIRROR if not already specified.\n\nif [ -z \"$ARCH\" ]; then\n    ARCH=\"`uname -m`\"\nfi\n\ncase \"$ARCH\" in\nx86 | i?86) ARCH=\"i386\";;\nx86_64 | amd64) ARCH=\"amd64\";;\narm64 | aarch64) ARCH=\"arm64\";;\narm*) ARCH=\"armhf\";;\n*) error 2 \"Invalid architecture '$ARCH'.\";;\nesac\n\n# trusty is the first release with arm64\nif [ \"$ARCH\" = \"arm64\" ] && release -lt trusty; then\n    ARCH=\"armhf\"\nfi\n\nif [ -z \"$MIRROR\" ]; then\n    if [ \"$ARCH\" = 'amd64' -o \"$ARCH\" = 'i386' ]; then\n        MIRROR=\"${CROUTON_MIRROR_ubuntu_x86:-http://archive.ubuntu.com/ubuntu/}\"\n    else\n        MIRROR=\"${CROUTON_MIRROR_ubuntu_arm:-http://ports.ubuntu.com/ubuntu-ports/}\"\n    fi\nfi\n\n"
  },
  {
    "path": "installer/ubuntu/getrelease.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nUSAGE=\"${0##*/} -a|-r /path/to/chroot\n\nDetects the release (-r) or arch (-a) of the chroot and prints it on stdout.\nFails with an error code of 1 if the chroot does not belong to this distro.\"\n\nif [ \"$#\" != 2 ] || [ \"$1\" != '-a' -a \"$1\" != '-r' ]; then\n    echo \"$USAGE\" 1>&2\n    exit 2\nfi\n\nsources=\"${2%/}/etc/apt/sources.list\"\nif [ ! -s \"$sources\" ]; then\n    exit 1\nfi\n\n# Lookup the release name from the field after the URI\n# We identify URI by '://'\nrel=\"`sed -n 's|^deb .*://[^ ]* \\([^ ]*\\) main\\( .*\\)\\?$|\\1|p' \\\n    \"$sources\" \"$sources.d\"/*.list 2>/dev/null | head -n 1`\"\nif [ -z \"$rel\" ] || \\\n        ! grep -q \"^$rel\\([^a-z].*\\)*$\" \"`dirname \"$0\"`/releases\"; then\n    exit 1\nfi\n\n# Print the architecture if requested\nif [ \"$1\" = '-a' ]; then\n    awk '/^Package: dpkg$/ {ok=1} ok && /^Architecture: / {print $2; exit}' \\\n        \"${2%/}/var/lib/dpkg/status\"\nelse\n    echo \"$rel\"\nfi\n\nexit 0\n"
  },
  {
    "path": "installer/ubuntu/pkgdetails",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# An adaption of pkgdetails.c from debootstrap into shell/mawk.\n\nAPPLICATION=\"${0##*/}\"\nUSAGE=\"\\\nusage: $APPLICATION PKGS mirror packagesfile pkgs..\n   or: $APPLICATION FIELD field mirror packagesfile pkgs..\n   or: $APPLICATION GETDEPS packagesfile pkgs..\n   or: $APPLICATION STANZAS packagesfile pkgs..\n   or: $APPLICATION WGET% low high end reason\"\n\n\n# Parses a packages file to extract the dependencies for a set of packages.\n# $1: pkgsfile\n# $2+: packages\n# Outputs a list of dependencies to stdout.\ndogetdeps() {\n    mawk -F': ' -f- \"$@\" <<EOF\n        BEGIN {\n            for (n = 2; n < ARGC; n++) {\n                pkgs[ARGV[n]] = 1\n                ARGV[n] = \"\"\n            }\n            npkgs = ARGC-2\n        }\n\n        /^Package:/ {\n            if (output && \\$2 != pkg) {\n                print output\n                if (pkg) {\n                    delete pkgs[pkg]\n                    npkgs--\n                }\n                if (!npkgs) {\n                    nextfile\n                }\n            }\n            output = \"\"\n            pkg = \\$2\n            if (!(pkg in pkgs)) {\n                pkg = \"\"\n            }\n        }\n\n        pkg && /^(Pre-)?Depends:/ {\n            # Parses the dependency list, spitting out the packages that are\n            # required, one per line. If multiple packages fulfil a dependency\n            # (as in, | is used), spit out the first one in the list.\n            split(\\$2, deps, \",\")\n            for (dep in deps) {\n                d = deps[dep]\n                match(d, \"[^ ][^ ]*\")\n                if (output) {\n                    output = output \"\\n\"\n                }\n                output = output substr(d, RSTART, RLENGTH)\n            }\n        }\n\n        END {\n            if (output) {\n                print output\n            }\n        }\nEOF\n    return 0\n}\n\n\n# Searches a package list for a set of packages that have a field that matches\n# the specified data.\n# $1: unique; set to y to exit once all items have been found once.\n# $2: fieldname\n# $3: mirror\n# $4: pkgsfile\n# $5+: packages\n# Outputs matching package data on stdout.\ndopkgmirrorpkgs() {\n    local unique=\"$1\" targetfield=\"$2\" mirror=\"$3\"\n    local checksumfield=\"${DEBOOTSTRAP_CHECKSUM_FIELD:-\"MD5sum\"}\"\n    shift 3\n\n    mawk -F': ' -f- \"$@\" <<EOF\n        BEGIN {\n            # Grab all the packages from the parameters, and zero them so that\n            # awk doesn't try to read them as files.\n            for (n = 2; n < ARGC; n++) {\n                pkgs[ARGV[n]] = 1\n                ARGV[n] = \"\"\n            }\n            npkgs = ARGC-2\n            skip = 2\n        }\n\n        # Empty lines mark the start of new packages\n        /^$/ {\n            if (skip == 0) {\n                output = pkg \" \" ver \" \" arch \" $mirror \" filename \" \" checksum \" \" size\n            }\n            skip = 2\n            next\n        }\n\n        # Do a case-insensitive search on the field name\n        /^${targetfield%\":\"}:/ {\n            if (\"$unique\" == \"y\" && \\$2 != field) {\n                if (field) {\n                    delete pkgs[field]\n                    npkgs--\n                }\n                if (!npkgs) {\n                    nextfile\n                }\n            }\n            field = \\$2\n            # See if it's in our list. If it's not, mark the item as skip so\n            # that we don't have to parse remaining fields.\n            skip = (field in pkgs) ? 0 : 1\n            if (skip) {\n                field = \"\"\n            }\n        }\n\n        # If we already know we don't care about this one, skip it.\n        (skip == 1) {\n            next\n        }\n\n        # Case-insensitive search on the checksum field name\n        /^${checksumfield%\":\"}:/ {\n            checksum = \\$2\n            next\n        }\n\n        # Handle other fields\n        /^Package:/ {\n            if (output && \\$2 != pkg) {\n                print output\n                output = \"\"\n            }\n            pkg = \\$2\n            next\n        }\n        /^Version:/ {\n            ver = \\$2\n            next\n        }\n        /^Architecture:/ {\n            arch = \\$2\n            next\n        }\n        /^Size:/ {\n            size = \\$2\n            next\n        }\n        /^Filename:/ {\n            filename = \\$2\n            next\n        }\n\n        END {\n            if (output) {\n                print output\n            }\n\n            # In unique mode, any that weren't found are returned as \"pkg -\"\n            if (\"$unique\" == \"y\" ) {\n                for (pkg in pkgs) {\n                    print pkg, \"-\"\n                }\n            }\n        }\nEOF\n    return 0\n}\n\n\n# Spits out the complete package info for the specified packages.\n# $1: pkgsfile\n# $2+: packages\ndopkgstanzas() {\n    mawk -F': ' -f- \"$@\" <<EOF\n        BEGIN {\n            for (n = 2; n < ARGC; n++) {\n                pkgs[ARGV[n]] = 1\n                ARGV[n] = \"\"\n            }\n            npkgs = ARGC-2\n            accum = \"\"\n            skip = 0\n        }\n\n        /^$/ {\n            if (!skip && accum) {\n                output = accum\n            }\n            accum = \"\"\n            skip = 0\n            next\n        }\n\n        /^Package:/ {\n            if (output && \\$2 != pkg) {\n                print output\n                output = \"\"\n                if (pkg) {\n                    delete pkgs[pkg]\n                    npkgs--\n                }\n                if (!npkgs) {\n                    nextfile\n                }\n            }\n            pkg = \\$2\n            skip = (pkg in pkgs) ? 0 : 1\n        }\n\n        (!skip) {\n            accum = accum \\$0 \"\\n\"\n        }\n\n        END {\n            if (output) {\n                print output\n            }\n        }\nEOF\n}\n\n\n# Print out anything that looks like a % on its own line, appropriately scaled\n# $1: low percent\n# $2: high percent\n# $3: end\n# $4: reason\ndotranslatewgetpercent() {\n    local input char lastval=0 val=0 low=\"$1\" high=\"$2\" ret=0\n    local suffix=\"$3${4:+\" \"}$4\"\n    # Use the tr method if we have stdbuf, as it is way faster.\n    if hash stdbuf 2>/dev/null; then\n        stdbuf -oL tr -sc '[:digit:]%' '\n' | {\n            while read -r input; do\n                val=\"${input%\"%\"}\"\n                if [ \"$input\" = \"$val\" ]; then\n                    continue\n                fi\n                lastval=\"$val\"\n                echo \"P: $((val*(high-low)/100+low)) $suffix\"\n            done\n            [ \"$lastval\" -eq 100 ]\n        } || ret=1\n    else\n        # Otherwise we have to use the head method, which is CPU intensive.\n        # Input may not have newlines, so process each character at a time.\n        # Grabbing one byte at a time is terribly slow, so we grab batches.\n        while input=\"`head -c8`\"; do\n            while [ -n \"$input\" ]; do\n                char=\"${input%\"${input#?}\"}\"\n                input=\"${input#\"$char\"}\"\n                case \"$char\" in\n                    [0-9]) val=$((val*10+char));;\n                    %) lastval=\"$val\"\n                       echo \"P: $((val*(high-low)/100+low)) $suffix\";;\n                    *) val=0;;\n                esac\n            done\n        done\n        [ \"$lastval\" -eq 100 ] || ret=1\n    fi\n    return \"$ret\"\n}\n\n\n# Process command\nextraparam=''\ncase \"$1\" in\n    WGET%)   minparams=3; cmd=dotranslatewgetpercent;;\n    GETDEPS) minparams=2; cmd=dogetdeps;;\n    PKGS)    minparams=3; cmd=dopkgmirrorpkgs; extraparam='y Package:';;\n    FIELD)   minparams=4; cmd=dopkgmirrorpkgs; extraparam='n';;\n    STANZAS) minparams=2; cmd=dopkgstanzas;;\n    *)       minparams=0; cmd='';;\nesac\n\n# Use -le to check number of parameters, since one parameter is the command.\nif [ -z \"$cmd\" -o \"$#\" -le \"$minparams\" ]; then\n    echo \"$USAGE\" 1>&2\n    exit 1\nfi\n\n# Dispatch\nshift\n\"$cmd\" $extraparam \"$@\"\nexit $?\n"
  },
  {
    "path": "installer/ubuntu/prepare",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This is a distro-specific continuation of the prepare.sh script.\n\nPKGEXT='deb'\nDISTROAKA='debian'\n\n\n# install_dist: see install() in prepare.sh for details.\ninstall_dist() {\n    local pkgs='' pkgsasdeps='' params='' asdeps=''\n    while [ \"$#\" != 0 ]; do\n        if [ \"$1\" = \"--minimal\" ]; then\n            params='--no-install-recommends'\n        elif [ \"$1\" = \"--asdeps\" ]; then\n            asdeps='y'\n        else\n            break\n        fi\n        shift\n    done\n    while [ \"$#\" != 0 ]; do\n        if [ \"$1\" = '--' ]; then\n            shift\n            break\n        fi\n        pkgs=\"$pkgs $1\"\n        shift\n    done\n    if [ -n \"$asdeps\" ]; then\n        pkgsasdeps=\"`list_uninstalled_dist '' $pkgs`\"\n    fi\n    apt-get -y $params install $pkgs `list_uninstalled_dist - \"$@\"` || return $?\n    if [ -n \"$pkgsasdeps\" ]; then\n        apt-mark auto $pkgsasdeps\n    fi\n}\n\n\n# install_pkg_dist: see install_pkg() in prepare.sh for details.\ninstall_pkg_dist() {\n    local params=''\n    if [ \"$1\" = '--minimal' ]; then\n        params='--no-install-recommends'\n        shift\n    fi\n    if [ \"$#\" != 0 ]; then\n        dpkg --force-depends -i \"$@\" || return $?\n    fi\n    apt-get -fy $params install\n}\n\n\n# install_dummy_dist: see install_dummy() in prepare.sh for details.\ninstall_dummy_dist() {\n    if [ \"$#\" = 0 ]; then\n        return\n    fi\n    local pkgname=\"crouton-$1\" pkgprovides=\"$1\"\n    shift\n    while [ \"$#\" != 0 ]; do\n        if [ \"$1\" = '--' ]; then\n            shift\n            break\n        fi\n        pkgprovides=\"$pkgprovides, $1\"\n        shift\n    done\n    local pkgdepends=\"$1\"\n    if [ \"$#\" != 0 ]; then\n        shift\n        while [ \"$#\" != 0 ]; do\n            pkgdepends=\"$pkgdepends, $1\"\n            shift\n        done\n    fi\n    local tmp=\"`mktemp -d crouton.XXXXXX --tmpdir=/tmp`\"\n    addtrap \"rm -rf '$tmp'\"\n    cat > \"$tmp/control\" <<EOF\nPackage: $pkgname\nVersion: 0\nArchitecture: all\nMaintainer: crouton\nInstalled-Size: 0\nProvides: $pkgprovides\nConflicts: $pkgprovides\nReplaces: $pkgprovides\nDepends: $pkgdepends\nSection: misc\nPriority: Required\nDescription: Provides a dummy ${pkgname#*-} for crouton\nEOF\n    touch \"$tmp/md5sums\"\n    echo '2.0' > \"$tmp/debian-binary\"\n    tar -czf \"$tmp/control.tar.gz\" -C \"$tmp\" control md5sums\n    tar -cf \"$tmp/data.tar\" -T /dev/null\n    ar r \"$tmp/$pkgname.deb\" \\\n        \"$tmp/debian-binary\" \"$tmp/control.tar.gz\" \"$tmp/data.tar\"\n    dpkg -i --force-depends \"$tmp/$pkgname.deb\"\n    apt-get -fy install\n}\n\n\n# remove_dist: see remove() in prepare.sh for details.\nremove_dist() {\n    apt-get -y --purge remove \"$@\"\n}\n\n\n# list_uninstalled_dist: see list_uninstalled() in prepare.sh for details.\n# If the package is virtual (e.g. libc-dev), we need to find the binary package\n# corresponding to it (e.g. libc6-dev), so that we can remove it afterwards\n# (\"apt-get remove libc-dev\" does not remove libc6-dev).\nlist_uninstalled_dist() {\n    local suffix=\"$1\" pkg\n    shift\n    for pkg in \"$@\"; do\n        # If $pkg is a binary package, apt-cache returns the package name\n        # itself, so $pkg value stays the same.\n        # If $pkg is a virtual package, it returns a list of binary packages\n        # that are able to provide it. If there is a single match, use that.\n        # If there is no match, it may mean that we are checking for a package\n        # that we installed manually from a .deb file, so we still use $pkg.\n        # If there is more than one match, we use $pkg, and apt-get install\n        # will fail due to selection ambiguity (it is not the purpose of this\n        # function to pick one of the alternative).\n        pkg=\"`apt-cache search --names-only \"^$pkg\\$\" |\n                  awk 'x{x=\"\"; exit} !x{x=$1} END{print x?x:\"'\"$pkg\"'\"}'`\"\n        if ! dpkg-query -l \"$pkg\" 2>/dev/null | grep -q '^[ih]i'; then\n            echo -n \"$pkg$suffix \"\n        fi\n    done\n}\n\n# detect_mirror\n# Detects the current mirror: Prints the first source that provides the\n# \"main\" component.\ndetect_mirror() {\n    if ! awk '/^deb .* main( .*)?$/ { print $2; f=1; exit }; END { exit !f }' \\\n          /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null; then\n        error 1 \"Cannot detect mirror.\"\n    fi\n}\n\n\n# install_mirror_package [--asdeps] package path [regex arch]\n# Fetch and install a package directly from the mirror.\n# This allows to install a package from a different release, when the current\n# release does not provide the required package, or when the version it\n# provides is broken or outdated.\n# --asdeps: Install as dependency\n# package:  Package name (e.g. xserver-xephyr)\n# path:     Mirror path (e.g. pool/universe/x/xorg-server)\n# regex:    Version regex (e.g. '1\\.11\\.4-0ubuntu[0-9][0-9]\\.[0-9]*').\n#           If not specified, match any version (default regex: \".*\").\n#           The latest version that matches the regex is selected.\n#           If the package is a dependency of other packages (e.g. a library),\n#           you should select the earliest version that provides the required\n#           features, to prevent breakage during release upgrade.\n# arch:     Additional architecture to install. e.g. i386 installs both i386\n#           and amd64 packages. Defaults to only installing \"$ARCH\"\ninstall_mirror_package() {\n    local asdeps=''\n    if [ \"$1\" = \"--asdeps\" ]; then\n        asdeps='y'\n        shift\n    fi\n    local package=\"$1\"\n    # Split local and assignment to make sure -e error handling works\n    local mirror\n    mirror=\"`detect_mirror`\"\n    local url=\"${mirror%/}/${2#/}/\"\n    local debfiles=\"\"\n    local pkgnames=\"\"\n\n    for carch in \"$ARCH\" \"$4\"; do\n        if [ -z \"$carch\" ]; then\n            continue\n        fi\n        local pkgname=\"$package:$carch\"\n        local regex=\"^${package}_${3:-.*}_(${carch}|all)\\.deb$\"\n        # Find package in directory listing:\n        # Filenames are HTML <a href> values, enclosed in quotes.\n        local xvers=\"`wget -O- \"$url\" -nv \\\n            | awk 'BEGIN { RS=\"<[aA][^>]* [hH][rR][eE][fF]=\\\\\"\"; FS=\"\\\\\"\" }\n                   NR > 1 && $1 ~ /'\"$regex\"'/ { print $1 }' \\\n            | sort -V | tail -n 1`\"\n        if [ -z \"$xvers\" ]; then\n            error 1 \"Error retrieving $pkgname.\"\n        fi\n        # Check if installed version is already up to date\n        local installed=\"`dpkg-query -l \"$pkgname\" 2>/dev/null | \\\n            awk '/^[hi]i/ { sub(/^[0-9]+:/, \"\", $3); print $2 \"_\" $3 }'`\"\n        if [ \"${xvers%_*}\" = \"$installed\" ]; then\n            echo \"$pkgname is already up to date ($installed).\"\n            continue\n        fi\n        pkgurl=\"$url$xvers\"\n        # Download the package to a temporary file\n        local deb=\"`mktemp crouton.XXXXXX --tmpdir=/tmp`\"\n        addtrap \"rm -f '$deb'\"\n        wget \"$pkgurl\" -O\"$deb\"\n        debfiles=\"$debfiles $deb\"\n        pkgnames=\"$pkgnames $pkgname\"\n    done\n\n    if [ -n \"$debfiles\" ]; then\n        # Install the package\n        install_pkg $debfiles\n        if [ -n \"$asdeps\" ]; then\n            apt-mark auto $pkgnames\n        fi\n    fi\n}\n\n# Run dpkg in non-interactive mode\nexport DEBIAN_FRONTEND=noninteractive\n\n# dpkg will auto start some services when installing, and it will ask\n# policy-rc.d for permissions. We don't want any service to start, so always\n# return denies.\necho exit 101 > /usr/sbin/policy-rc.d\nchmod +x /usr/sbin/policy-rc.d\n\n# Debian glibc won't install on kernel revisions >=255, so add a wrapper script\n# to clamp the revision number. Without also patching the uname syscalls,\n# glibc's version math will overflow, resulting in an increased minor version\n# number and possibly in glibc trying to use unavailable syscalls or features.\nif [ ! -e /bin/uname.bin -a ! -e /usr/bin/uname.bin ]; then\n    for uname in /bin/uname /usr/bin/uname; do\n        # We don't use --rename since /bin may be a temp symlink to /usr/bin.\n        # --no-rename doesn't exist on old dpkg, but --rename will become the\n        # default in the future, so we have to try both with and without.\n        dpkg-divert --divert \"$uname.bin\" --no-rename \"$uname\" 2>/dev/null \\\n            || dpkg-divert --divert \"$uname.bin\" \"$uname\"\n        mv -f \"$uname\" \"$uname.bin\" 2>/dev/null || true\n    done\n    echo '#!/bin/sh -eu\nuname.bin \"$@\" | sed '\\''s/\\.\\(25[5-9]\\|2[6-9][0-9]\\|[3-9][0-9][0-9]\\)-/.254-/g'\\' > /bin/uname\n    chmod +x /bin/uname\nfi\n\n# Run debootstrap second stage if it hasn't already happened\nif [ -r /debootstrap ]; then\n    # Debootstrap doesn't like anything mounted under /sys or /var when it runs\n    # We request a re-mount after bootstrapping, so these mounts will be fixed.\n    sed \"s=\\\\\\\\040=//=g\" /proc/mounts | cut -d' ' -f2 \\\n        | grep -e '^/sys/.' -e '^/var/run' \\\n        | sed -e 's=//= =g;s/^\\(\\(.*\\) (deleted)\\)$/\\1\\n\\2/' \\\n        | sort -r | xargs --no-run-if-empty -d '\n' -n 1 umount || true\n    # Workaround for some kernel versions where debootstrap second stage\n    # crashes when attempting to generate /etc/machine-id (issue #4016).\n    date -Ins | md5sum | cut -f1 -d' ' > /etc/machine-id\n    # Start the bootstrap\n    /debootstrap/debootstrap --second-stage\n    # Clean contents of /var/run (since we'll always be mounting over it).\n    rm -rf --one-file-system /var/run/*\n    # Request a script restart to refresh the mount environment\n    relaunch_setup\nelse\n    # Do any pending configuration, in case of an unfortunately-timed Ctrl-C\n    dpkg --configure -a\n\n    # Fix the keyboard mode in case it got reverted to raw\n    fixkeyboardmode\nfi\n"
  },
  {
    "path": "installer/ubuntu/releases",
    "content": "warty!\nhoary!\nbreezy!\ndapper!\nedgy!\nfeisty!\ngutsy!\nhardy!\nintrepid!\njaunty!\nkarmic!\nlucid!\nmaverick!\nnatty!\noneiric!\nprecise!\nquantal!\nraring!\nsaucy!\ntrusty!\nutopic!\nvivid!\nwily!\nxenial\nyakkety!\nzesty!\nartful!\nbionic*\ncosmic!\ndisco!\neoan!\nfocal\ngroovy!\nhirsute!\nimpish!\njammy*\nkinetic!\nlunar!\nmantic*\nnoble*\noracular*\n"
  },
  {
    "path": "src/fbserver-proto.h",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * WebSocket fbserver shared structures.\n *\n */\n\n#ifndef FB_SERVER_PROTO_H_\n#define FB_SERVER_PROTO_H_\n\n#include <stdint.h>\n\n/* WebSocket constants */\n#define VERSION \"VF3\"\n#define PORT_BASE 30010\n\n/* Request for a frame */\nstruct  __attribute__((__packed__)) screen {\n    char type;  /* 'S' */\n    uint8_t shm:1;  /* Transfer data through shm */\n    uint8_t refresh:1;  /* Force a refresh, even if no damage is observed */\n    uint16_t width;\n    uint16_t height;\n    uint64_t paddr;  /* shm: client buffer address */\n    uint64_t sig;  /* shm: signature at the beginning of buffer */\n};\n\n/* Reply to request for a frame */\nstruct  __attribute__((__packed__)) screen_reply {\n    char type;  /* 'S' */\n    uint8_t shm:1;  /* Data was transfered through shm */\n    uint8_t shmfailed:1;  /* shm trick has failed */\n    uint8_t updated:1;  /* data has been updated (Xdamage) */\n    uint8_t cursor_updated:1;  /* cursor has been updated */\n    uint16_t width;\n    uint16_t height;\n    uint32_t cursor_serial;  /* Cursor to display */\n};\n\n/* Request for cursor image (if cursor_serial is unknown) */\nstruct  __attribute__((__packed__)) cursor {\n    char type;  /* 'P' */\n};\n\n/* Reply to requets for a cursor image (variable length) */\nstruct  __attribute__((__packed__)) cursor_reply {\n    char type;  /* 'P' */\n    uint16_t width, height;\n    uint16_t xhot, yhot;  /* \"Hot\" coordinates */\n    uint32_t cursor_serial;  /* X11 unique serial number */\n    uint32_t pixels[0];  /* Payload, 32-bit per pixel */\n};\n\n/* Change resolution (query + reply) */\nstruct  __attribute__((__packed__)) resolution {\n    char type;  /* 'R' */\n    uint16_t width;\n    uint16_t height;\n};\n\n/* Press a key */\nstruct  __attribute__((__packed__)) key {\n    char type;  /* 'K' */\n    uint8_t down:1;  /* 1: down, 0: up */\n    uint8_t keycode;  /* X11 KeyCode (8-255) */\n};\n\n/* Press a key (compatibility with VF1) */\n/* TODO: Remove support for VF1. */\nstruct  __attribute__((__packed__)) key_vf1 {\n    char type;  /* 'K' */\n    uint8_t down:1;  /* 1: down, 0: up */\n    uint32_t keysym;  /* X11 KeySym */\n};\n\n/* Move the mouse */\nstruct  __attribute__((__packed__)) mousemove {\n    char type;  /* 'M' */\n    uint16_t x;\n    uint16_t y;\n};\n\n/* Send initialization info */\nstruct  __attribute__((__packed__)) initinfo {\n    char type; /* I */\n    uint8_t freon; /* 0: not using freon, 1: using freon */\n};\n\n/* Click the mouse */\nstruct  __attribute__((__packed__)) mouseclick {\n    char type;  /* 'C' */\n    uint8_t down:1;\n    uint8_t button;  /* X11 button number (e.g. 1 is left) */\n};\n\n#endif  /* FB_SERVER_PROTO_H_ */\n"
  },
  {
    "path": "src/fbserver.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * WebSocket server that acts as a X11 framebuffer server. It communicates\n * with the extension in Chromium OS. It sends framebuffer and cursor data,\n * and receives keyboard/mouse events.\n *\n */\n\n#include \"websocket.h\"\n#include \"fbserver-proto.h\"\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/mman.h>\n#include <netinet/tcp.h>\n#include <X11/extensions/XTest.h>\n#include <X11/Xatom.h>\n#include <X11/Xlib.h>\n#include <X11/Xutil.h>\n#include <sys/shm.h>\n#include <X11/extensions/XShm.h>\n#include <X11/extensions/Xdamage.h>\n#include <X11/extensions/Xfixes.h>\n#include <sys/un.h>\n\nconst char *SOCKET_PATH = \"/var/run/crouton-ext/socket\";\n\n/* X11-related variables */\nstatic Display *dpy;\nstatic int damageEvent;\nstatic int fixesEvent;\n\n/* shm entry cache */\nstruct cache_entry {\n    uint64_t paddr; /* Address from PNaCl side */\n    int fd;\n    void *map; /* mmap-ed memory */\n    size_t length; /* mmap length */\n};\n\nstatic struct cache_entry cache[2];\nstatic int next_entry;\n\n/* Remember which keys/buttons are currently pressed */\ntypedef enum { MOUSE=1, KEYBOARD=2 } keybuttontype;\nstruct keybutton {\n    keybuttontype type;\n    uint32_t code;  /* KeyCode or mouse button */\n};\n\n/* Store currently pressed keys/buttons in an array.\n * No valid entry on or after curmax. */\nstatic struct keybutton pressed[256];\nstatic int pressed_len = 0;\n\n/* Adds a key/button to array of pressed keys */\nvoid kb_add(keybuttontype type, uint32_t code) {\n    trueorabort(pressed_len < sizeof(pressed)/sizeof(struct keybutton),\n                \"Too many keys pressed\");\n\n    int i;\n    for (i = 0; i < pressed_len; i++) {\n        if (pressed[i].type == type && pressed[i].code == code)\n            return;\n    }\n\n    pressed[pressed_len].type = type;\n    pressed[pressed_len].code = code;\n    pressed_len++;\n}\n\n/* Removes a key/button from array of pressed keys */\nvoid kb_remove(keybuttontype type, uint32_t code) {\n    int i;\n    for (i = 0; i < pressed_len; i++) {\n        if (pressed[i].type == type && pressed[i].code == code) {\n            if (i < pressed_len-1)\n                pressed[i] = pressed[pressed_len-1];\n\n            pressed_len--;\n            return;\n        }\n    }\n}\n\n/* Releases all pressed key/buttons, and empties array */\nvoid kb_release_all() {\n    int i;\n    log(2, \"Releasing all keys...\");\n    for (i = 0; i < pressed_len; i++) {\n        if (pressed[i].type == MOUSE) {\n            log(2, \"Mouse %d\", pressed[i].code);\n            XTestFakeButtonEvent(dpy, pressed[i].code, 0, CurrentTime);\n        } else if (pressed[i].type == KEYBOARD) {\n            log(2, \"Keyboard %d\", pressed[i].code);\n            XTestFakeKeyEvent(dpy, pressed[i].code, 0, CurrentTime);\n        }\n    }\n    pressed_len = 0;\n}\n\n/* X11-related functions */\n\nstatic int xerror_handler(Display *dpy, XErrorEvent *e) {\n    if (verbose < 1)\n        return 0;\n    char msg[64] = {0};\n    char op[32] = {0};\n    sprintf(msg, \"%d\", e->request_code);\n    XGetErrorDatabaseText(dpy, \"XRequest\", msg, \"\", op, sizeof(op));\n    XGetErrorText(dpy, e->error_code, msg, sizeof(msg));\n    error(\"%s (%s)\", msg, op);\n    return 0;\n}\n\n/* Sets the CROUTON_CONNECTED property for the root window */\nstatic void set_connected(Display *dpy, uint8_t connected) {\n    Window root = DefaultRootWindow(dpy);\n    Atom prop = XInternAtom(dpy, \"CROUTON_CONNECTED\", False);\n    if (prop == None) {\n        error(\"Unable to get atom\");\n        return;\n    }\n    XChangeProperty(dpy, root, prop, XA_INTEGER, 8, PropModeReplace,\n                    &connected, 1);\n    XFlush(dpy);\n}\n\n/* Registers XDamage events for a given Window. */\nstatic void register_damage(Display *dpy, Window win) {\n    XWindowAttributes attrib;\n    if (XGetWindowAttributes(dpy, win, &attrib) &&\n            !attrib.override_redirect) {\n        XDamageCreate(dpy, win, XDamageReportRawRectangles);\n    }\n}\n\n/* Connects to the X11 display, initializes extensions, register for events */\nstatic int init_display(char* name) {\n    dpy = XOpenDisplay(name);\n\n    if (!dpy) {\n        error(\"Cannot open display.\");\n        return -1;\n    }\n\n    /* We need XTest, XDamage and XFixes */\n    int event, error, major, minor;\n    if (!XTestQueryExtension(dpy, &event, &error, &major, &minor)) {\n        error(\"XTest not available!\");\n        return -1;\n    }\n\n    if (!XDamageQueryExtension(dpy, &damageEvent, &error)) {\n        error(\"XDamage not available!\");\n        return -1;\n    }\n\n    if (!XFixesQueryExtension(dpy, &fixesEvent, &error)) {\n        error(\"XFixes not available!\");\n        return -1;\n    }\n\n    /* Get notified when new windows are created. */\n    Window root = DefaultRootWindow(dpy);\n    XSelectInput(dpy, root, SubstructureNotifyMask);\n\n    /* Register damage events for existing windows */\n    Window rootp, parent;\n    Window *children;\n    unsigned int i, nchildren;\n    XQueryTree(dpy, root, &rootp, &parent, &children, &nchildren);\n\n    /* FIXME: We never reset the handler, is that a good thing? */\n    XSetErrorHandler(xerror_handler);\n\n    register_damage(dpy, root);\n    for (i = 0; i < nchildren; i++) {\n        register_damage(dpy, children[i]);\n    }\n\n    XFree(children);\n\n    /* Register for cursor events */\n    XFixesSelectCursorInput(dpy, root, XFixesDisplayCursorNotifyMask);\n\n    return 0;\n}\n\n/* Changes resolution using external handler.\n * Reply must be a resolution in \"canonical\" form: <w>x<h>[_<rate>] */\n/* FIXME: Maybe errors here should not be fatal... */\nvoid change_resolution(const struct resolution* rin) {\n    /* Setup parameters and run command */\n    char arg1[32], arg2[32];\n    int c;\n    c = snprintf(arg1, sizeof(arg1), \"%d\", rin->width);\n    trueorabort(c > 0, \"snprintf\");\n    c = snprintf(arg2, sizeof(arg2), \"%d\", rin->height);\n    trueorabort(c > 0, \"snprintf\");\n\n    char* cmd = \"setres\";\n    char* args[] = {cmd, arg1, arg2, NULL};\n    char buffer[256];\n    log(2, \"Running %s %s %s\", cmd, arg1, arg2);\n    c = popen2(cmd, args, NULL, 0, buffer, sizeof(buffer));\n    trueorabort(c > 0, \"popen2\");\n\n    /* Parse output */\n    buffer[c < sizeof(buffer) ? c : (sizeof(buffer)-1)] = 0;\n    log(2, \"Result: %s\", buffer);\n    char* cut = strchr(buffer, '_');\n    if (cut) *cut = 0;\n    cut = strchr(buffer, 'x');\n    trueorabort(cut, \"Invalid answer: %s\", buffer);\n    *cut = 0;\n\n    char* endptr;\n    long nwidth = strtol(buffer, &endptr, 10);\n    trueorabort(buffer != endptr && *endptr == '\\0',\n                \"Invalid width: '%s'\", buffer);\n    long nheight = strtol(cut+1, &endptr, 10);\n    trueorabort(cut+1 != endptr && (*endptr == '\\0' || *endptr == '\\n'),\n                \"Invalid height: '%s'\", cut+1);\n    log(1, \"New resolution %ld x %ld\", nwidth, nheight);\n\n    char reply_raw[FRAMEMAXHEADERSIZE + sizeof(struct resolution)];\n    struct resolution* r = (struct resolution*)(reply_raw + FRAMEMAXHEADERSIZE);\n    r->type = 'R';\n    r->width = nwidth;\n    r->height = nheight;\n    socket_client_write_frame(reply_raw, sizeof(*r), WS_OPCODE_BINARY, 1);\n}\n\n/* Closes the mmap/fd in the entry. */\nvoid close_mmap(struct cache_entry* entry) {\n    if (!entry->map)\n        return;\n\n    log(2, \"Closing mmap %p %zu %d\", entry->map, entry->length, entry->fd);\n    munmap(entry->map, entry->length);\n    close(entry->fd);\n    entry->map = NULL;\n}\n\n/* Read the pid of nacl_helper and get shm from findnacl daemon.\n * The socket fd is passed in and fd of nacl_helper is returned..*/\nint recv_pid_fd(int conn)\n{\n    int fd = -1;\n    struct msghdr msg = { 0 };\n    struct iovec iov;\n    struct cmsghdr *cmsg;\n    long pid;\n    char buf[CMSG_SPACE(sizeof(int))];\n\n    memset(buf, 0, sizeof(buf));\n\n    iov.iov_base = &pid;\n    iov.iov_len = sizeof(pid);\n    msg.msg_iov = &iov;\n    msg.msg_iovlen = 1;\n    msg.msg_control = buf;\n    msg.msg_controllen = CMSG_SPACE(sizeof(int));\n\n    if (recvmsg(conn, &msg, 0) < 0) {\n        syserror(\"Cannot get response from findnacl daemon.\");\n        return -1;\n    }\n\n    cmsg = CMSG_FIRSTHDR(&msg);\n    if (cmsg) {\n        if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {\n            fd = *((int *)CMSG_DATA(cmsg));\n        } else {\n            error(\"No fd is passed from findnacl daemon.\");\n        }\n    } else {\n        error(\"No fd is passed from findnacl daemon.\");\n    }\n    return fd;\n}\n\n/* Finds NaCl/Chromium shm memory using external handler.\n * Reply must be in the form PID:file */\nstruct cache_entry* find_shm(uint64_t paddr, uint64_t sig, size_t length) {\n    struct cache_entry* entry = NULL;\n\n    /* Find entry in cache */\n    if (cache[0].paddr == paddr) {\n        entry = &cache[0];\n    } else if (cache[1].paddr == paddr) {\n        entry = &cache[1];\n    } else {\n        /* Not found: erase an existing entry. */\n        entry = &cache[next_entry];\n        next_entry = (next_entry + 1) % 2;\n        close_mmap(entry);\n    }\n\n    int try;\n    for (try = 0; try < 2; try++) {\n        /* Check signature */\n        if (entry->map) {\n            if (*((uint64_t*)entry->map) == sig)\n                return entry;\n\n            log(1, \"Invalid signature, fetching new shm!\");\n            close_mmap(entry);\n        }\n\n        /* Setup parameters and run command */\n        char arg1[32], arg2[32];\n        int c;\n\n        c = snprintf(arg1, sizeof(arg1), \"%08lx\", (long)paddr & 0xffffffff);\n        trueorabort(c > 0, \"snprintf\");\n        int i, p = 0;\n        for (i = 0; i < 8; i++) {\n            c = snprintf(arg2 + p, sizeof(arg2) - p, \"%02x\",\n                         ((uint8_t*)&sig)[i]);\n            trueorabort(c > 0, \"snprintf\");\n            p += c;\n        }\n\n        int sock;\n        struct sockaddr_un addr;\n\n        sock = socket(AF_UNIX, SOCK_STREAM, 0);\n        addr.sun_family = AF_UNIX;\n        strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path));\n\n        if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {\n            syserror(\"Cannot connect to findnacl daemon.\");\n            return NULL;\n        }\n\n        char args[70];\n        c = snprintf(args, sizeof(args), \"%s %s\", arg1, arg2);\n        trueorabort(c > 0 && c < sizeof(args), \"snprintf\");\n\n        if (write(sock, args, strlen(args)) < 0) {\n            syserror(\"Cannot send arguments.\");\n            close(sock);\n            return NULL;\n        }\n\n        entry->fd = recv_pid_fd(sock);\n        if (entry->fd < 0) {\n            error(\"Cannot open nacl file.\");\n            return NULL;\n        }\n        close(sock);\n\n        entry->paddr = paddr;\n\n        entry->length = length;\n        entry->map = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED,\n                          entry->fd, 0);\n        if (!entry->map) {\n            error(\"Cannot mmap.\");\n            close(entry->fd);\n            return NULL;\n        }\n\n        log(2, \"mmap ok %p %zu %d\", entry->map, entry->length, entry->fd);\n    }\n\n    error(\"Cannot find shm.\");\n    return NULL;\n}\n\n/* WebSocket functions */\n\nXImage* img = NULL;\nXShmSegmentInfo shminfo;\n\n/* Writes framebuffer image to websocket/shm */\nint write_image(const struct screen* screen) {\n    char reply_raw[FRAMEMAXHEADERSIZE + sizeof(struct screen_reply)];\n    struct screen_reply* reply =\n        (struct screen_reply*)(reply_raw + FRAMEMAXHEADERSIZE);\n    int refresh = 0;\n\n    memset(reply_raw, 0, sizeof(reply_raw));\n\n    reply->type = 'S';\n    reply->width = screen->width;\n    reply->height = screen->height;\n\n    /* Allocate XShmImage */\n    if (!img || img->width != screen->width || img->height != screen->height) {\n        if (img) {\n            XDestroyImage(img);\n            shmdt(shminfo.shmaddr);\n            shmctl(shminfo.shmid, IPC_RMID, 0);\n        }\n\n        /* FIXME: Some error checking should happen here... */\n        img = XShmCreateImage(dpy, DefaultVisual(dpy, 0), 24,\n                              ZPixmap, NULL, &shminfo,\n                              screen->width, screen->height);\n        trueorabort(img, \"XShmCreateImage\");\n        shminfo.shmid = shmget(IPC_PRIVATE, img->bytes_per_line*img->height,\n                               IPC_CREAT|0777);\n        trueorabort(shminfo.shmid != -1, \"shmget\");\n        shminfo.shmaddr = img->data = shmat(shminfo.shmid, 0, 0);\n        trueorabort(shminfo.shmaddr != (void*)-1, \"shmat\");\n        shminfo.readOnly = False;\n        int ret = XShmAttach(dpy, &shminfo);\n        trueorabort(ret, \"XShmAttach\");\n        /* Force refresh */\n        refresh = 1;\n    }\n\n    if (screen->refresh) {\n        log(1, \"Force refresh from client.\");\n        /* refresh forced by the client */\n        refresh = 1;\n    }\n\n    XEvent ev;\n    /* Register damage on new windows */\n    while (XCheckTypedEvent(dpy, MapNotify, &ev)) {\n        register_damage(dpy, ev.xcreatewindow.window);\n        refresh = 1;\n    }\n\n    /* Check for damage */\n    while (XCheckTypedEvent(dpy, damageEvent + XDamageNotify, &ev)) {\n        refresh = 1;\n    }\n\n    /* Check for cursor events */\n    reply->cursor_updated = 0;\n    while (XCheckTypedEvent(dpy, fixesEvent + XFixesCursorNotify, &ev)) {\n        XFixesCursorNotifyEvent* curev = (XFixesCursorNotifyEvent*)&ev;\n        if (verbose >= 2) {\n            char* name = XGetAtomName(dpy, curev->cursor_name);\n            log(2, \"cursor! %ld %s\", curev->cursor_serial, name);\n            XFree(name);\n        }\n        reply->cursor_updated = 1;\n        reply->cursor_serial = curev->cursor_serial;\n    }\n\n    /* No update */\n    if (!refresh) {\n        reply->shm = 0;\n        reply->updated = 0;\n        socket_client_write_frame(reply_raw, sizeof(*reply),\n                                  WS_OPCODE_BINARY, 1);\n        return 0;\n    }\n\n    /* Get new image from framebuffer */\n    XShmGetImage(dpy, DefaultRootWindow(dpy), img, 0, 0, AllPlanes);\n\n    int size = img->bytes_per_line * img->height;\n\n    trueorabort(size == screen->width*screen->height*4,\n                \"Invalid screen byte count\");\n\n    trueorabort(screen->shm, \"Non-SHM rendering is not supported\");\n\n    struct cache_entry* entry = find_shm(screen->paddr, screen->sig, size);\n\n    reply->shm = 1;\n    reply->updated = 1;\n    reply->shmfailed = 0;\n\n    if (entry && entry->map) {\n        if (size == entry->length) {\n            memcpy(entry->map, img->data, size);\n            msync(entry->map, size, MS_SYNC);\n        } else {\n            /* This should never happen (it means the client passed an\n             * outdated buffer to us). */\n            error(\"Invalid shm entry length (client bug!).\");\n            reply->shmfailed = 1;\n        }\n    } else {\n        /* Keep the flow going, even if we cannot find the shm. Next time\n         * the NaCl client reallocates the buffer, we are likely to be able\n         * to find it. */\n        error(\"Cannot find shm, moving on...\");\n        reply->shmfailed = 1;\n    }\n\n    /* Confirm write is done */\n    socket_client_write_frame(reply_raw, sizeof(*reply),\n                              WS_OPCODE_BINARY, 1);\n\n    return 0;\n}\n\n/* Writes cursor image to websocket */\nint write_cursor() {\n    XFixesCursorImage *img = XFixesGetCursorImage(dpy);\n    if (!img) {\n        error(\"XFixesGetCursorImage returned NULL\");\n        return -1;\n    }\n    int size = img->width*img->height;\n    const int replylength = sizeof(struct cursor_reply) + size*sizeof(uint32_t);\n    char reply_raw[FRAMEMAXHEADERSIZE + replylength];\n    struct cursor_reply* reply =\n        (struct cursor_reply*)(reply_raw + FRAMEMAXHEADERSIZE);\n\n    memset(reply_raw, 0, sizeof(*reply_raw));\n\n    reply->type = 'P';\n    reply->width = img->width;\n    reply->height = img->height;\n    reply->xhot = img->xhot;\n    reply->yhot = img->yhot;\n    reply->cursor_serial = img->cursor_serial;\n    /* This casts long[] to uint32_t[] */\n    int i;\n    for (i = 0; i < size; i++)\n        reply->pixels[i] = img->pixels[i];\n\n    socket_client_write_frame(reply_raw, replylength, WS_OPCODE_BINARY, 1);\n    XFree(img);\n\n    return 0;\n}\n\nvoid write_init() {\n    char raw[FRAMEMAXHEADERSIZE + sizeof(struct initinfo)];\n    struct initinfo* i = (struct initinfo*)(raw + FRAMEMAXHEADERSIZE);\n    i->type = 'I';\n    i->freon = 0;\n    if (access(\"/sys/class/tty/tty0/active\", F_OK) == -1) {\n        trueorabort(errno == ENOENT, \"Could not determine if using Freon or not\");\n        i->freon = 1;\n    }\n    socket_client_write_frame(raw, sizeof(*i), WS_OPCODE_BINARY, 1);\n}\n\n/* Checks if a packet size is correct */\nint check_size(int length, int target, char* error) {\n    if (length != target) {\n        error(\"Invalid %s packet (%d != %d)\", error, length, target);\n        socket_client_close(0);\n        return 0;\n    }\n    return 1;\n}\n\n/* Prints usage */\nvoid usage(char* argv0) {\n    fprintf(stderr, \"%s [-v 0-3] display\\n\", argv0);\n    exit(1);\n}\n\nint main(int argc, char** argv) {\n    int c;\n    while ((c = getopt(argc, argv, \"v:\")) != -1) {\n        switch (c) {\n        case 'v':\n            verbose = atoi(optarg);\n            break;\n        default:\n            usage(argv[0]);\n        }\n    }\n\n    if (optind != argc-1)\n        usage(argv[0]);\n\n    char* display = argv[optind];\n\n    trueorabort(display[0] == ':', \"Invalid display: '%s'\", display);\n\n    char* endptr;\n    int displaynum = (int)strtol(display+1, &endptr, 10);\n    trueorabort(display+1 != endptr && (*endptr == '\\0' || *endptr == '.'),\n                \"Invalid display number: '%s'\", display);\n\n    init_display(display);\n    socket_server_init(PORT_BASE + displaynum);\n\n    unsigned char buffer[BUFFERSIZE];\n    int length;\n\n    while (1) {\n        set_connected(dpy, False);\n        socket_server_accept(VERSION);\n        write_init();\n        set_connected(dpy, True);\n        while (1) {\n            length = socket_client_read_frame((char*)buffer, sizeof(buffer));\n            if (length < 0) {\n                socket_client_close(1);\n                break;\n            }\n\n            if (length < 1) {\n                error(\"Invalid packet from client (size <1).\");\n                socket_client_close(0);\n                break;\n            }\n\n            switch (buffer[0]) {\n            case 'S':  /* Screen */\n                if (!check_size(length, sizeof(struct screen), \"screen\"))\n                    break;\n                write_image((struct screen*)buffer);\n                break;\n            case 'P':  /* Cursor */\n                if (!check_size(length, sizeof(struct cursor), \"cursor\"))\n                    break;\n                write_cursor();\n                break;\n            case 'R':  /* Resolution */\n                if (!check_size(length, sizeof(struct resolution),\n                                \"resolution\"))\n                    break;\n                change_resolution((struct resolution*)buffer);\n                break;\n            case 'K': {  /* Key */\n                if (!check_size(length, sizeof(struct key), \"key\"))\n                    break;\n                struct key* k = (struct key*)buffer;\n                log(2, \"Key: kc=%04x\\n\", k->keycode);\n                XTestFakeKeyEvent(dpy, k->keycode, k->down, CurrentTime);\n                if (k->down) {\n                    kb_add(KEYBOARD, k->keycode);\n                } else {\n                    kb_remove(KEYBOARD, k->keycode);\n                }\n                break;\n            }\n            case 'C': {  /* Click */\n                if (!check_size(length, sizeof(struct mouseclick),\n                                \"mouseclick\"))\n                    break;\n                struct mouseclick* mc = (struct mouseclick*)buffer;\n                XTestFakeButtonEvent(dpy, mc->button, mc->down, CurrentTime);\n                if (mc->down) {\n                    kb_add(MOUSE, mc->button);\n                } else {\n                    kb_remove(MOUSE, mc->button);\n                }\n                break;\n            }\n            case 'M': {  /* Mouse move */\n                if (!check_size(length, sizeof(struct mousemove), \"mousemove\"))\n                    break;\n                struct mousemove* mm = (struct mousemove*)buffer;\n                XTestFakeMotionEvent(dpy, 0, mm->x, mm->y, CurrentTime);\n                break;\n            }\n            case 'Q':  /* \"Quit\": release all keys */\n                kb_release_all();\n                break;\n            default:\n                error(\"Invalid packet from client (%d).\", buffer[0]);\n                socket_client_close(0);\n            }\n        }\n        socket_client_close(0);\n        kb_release_all();\n        close_mmap(&cache[0]);\n        close_mmap(&cache[1]);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/findnacld.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n#include \"websocket.h\"\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <sys/epoll.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/select.h>\n#include <stddef.h>\n\n#define MAX_EVENTS 100\n\nconst char *SOCKET_DIR = \"/var/run/crouton-ext\";\nconst char *SOCKET_PATH = \"/var/run/crouton-ext/socket\";\n\nint send_pid_fd(int conn, long pid, int fd)\n{\n    struct msghdr msg = {0};\n    struct cmsghdr *cmsg;\n    struct iovec iov;\n    char buf[CMSG_SPACE(sizeof(int))]; /* ancillary data buffer */\n\n    /* It is not necessary to sent the pid. However, to pass fd using sendmsg,\n     * at least 1 byte of data must be sent.\n     */\n    iov.iov_base = &pid;\n    iov.iov_len = sizeof(pid);\n\n    msg.msg_iov = &iov;\n    msg.msg_iovlen = 1;\n    if (fd > 0) {\n        msg.msg_control = buf;\n        msg.msg_controllen = sizeof(buf);\n\n        cmsg = CMSG_FIRSTHDR(&msg);\n        cmsg->cmsg_level = SOL_SOCKET;\n        cmsg->cmsg_type = SCM_RIGHTS;\n        cmsg->cmsg_len = CMSG_LEN(sizeof(int));\n\n        *((int *)CMSG_DATA(cmsg)) = fd;\n    } else {\n        msg.msg_control = NULL;\n        msg.msg_controllen = 0;\n    }\n\n    return sendmsg(conn, &msg, 0);\n}\n\n\nint find_nacl(int conn)\n{\n    char argbuf[70], outbuf[256];\n    char* cut;\n    int c;\n\n    if ((c = read(conn, argbuf, sizeof(argbuf)-1)) < 0) {\n       syserror(\"Failed to read arguments\");\n       return -1;\n    }\n    argbuf[c] = 0;\n\n    cut = strchr(argbuf, ' ');\n    if (!cut) {\n        error(\"No ' ' in findnacl arguments: %s.\", argbuf);\n        return -1;\n    }\n    *cut = 0;\n\n    char *cmd = \"croutonfindnacl\";\n    char* args[] = {cmd, argbuf, cut + 1, NULL};\n\n    c = popen2(cmd, args, NULL, 0, outbuf, sizeof(outbuf)-1);\n    if (c <= 0) {\n        error(\"Error running helper\");\n        return -1;\n    }\n    outbuf[c] = 0;\n\n    /* Parse PID:file output */\n    cut = strchr(outbuf, ':');\n    if (!cut) {\n        error(\"No ':' in helper reply: %s.\", outbuf);\n        return -1;\n    }\n    *cut = 0;\n\n    char* endptr;\n    long pid = strtol(outbuf, &endptr, 10);\n    if(outbuf == endptr || *endptr != '\\0') {\n        error(\"Invalid pid: %s\", outbuf);\n        return -1;\n    }\n\n    char* file = cut+1;\n    int ret = 0;\n    int fd = -1;\n    if (pid > 0) {\n        if ((fd = open(file, O_RDWR)) < 0)\n            syserror(\"Cannot open file %s\", file);\n    }\n\n    if (send_pid_fd(conn, pid, fd) < 0) {\n        syserror(\"FD-passing failed.\");\n        ret = -1;\n    }\n\n    close(fd);\n    return ret;\n}\n\nint main()\n{\n    int sock, conn, fd;\n    int maxfd;\n    struct sockaddr_un addr;\n    fd_set readset, recvset;\n\n    /* Set egid to be 27 (video) and change the umask to 007,\n     * so that normal user can also access the socket if they\n     * are in video group.\n     */\n    if (setegid(27) < 0) {\n        syserror(\"Cannot set gid to 27\");\n        return -1;\n    }\n    umask(S_IROTH | S_IWOTH | S_IXOTH);\n\n    if (mkdir(SOCKET_DIR, 0770) < 0) {\n        if (errno != EEXIST) {\n            syserror(\"Cannot create %s\", SOCKET_DIR);\n            return -1;\n        }\n    }\n\n    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {\n        syserror(\"Failed to create socket.\");\n        return -1;\n    }\n\n    memset(&addr, 0, sizeof(addr));\n    addr.sun_family = AF_UNIX;\n    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path));\n\n    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {\n        syserror(\"Failed to bind address: %s.\", SOCKET_PATH);\n        return -1;\n    }\n\n    if (listen(sock, 1024) < 0) {\n        syserror(\"Failed to listen on %s.\", SOCKET_PATH);\n        return -1;\n    }\n\n    FD_ZERO(&readset);\n    FD_SET(sock, &readset);\n\n    maxfd = sock;\n\n    for (;;) {\n        memcpy(&recvset, &readset, sizeof(recvset));\n        select(maxfd + 1, &recvset, NULL, NULL, NULL);\n\n        for (fd = 0; fd <= maxfd; fd++) {\n            if (FD_ISSET(fd, &recvset)) {\n                if (fd == sock) {\n                    conn = accept(sock, NULL, 0);\n                    if (conn < 0) {\n                        syserror(\"Connection error.\");\n                        continue;\n                    }\n                    if (conn > maxfd)\n                        maxfd = conn;\n                    FD_SET(conn, &readset);\n                }\n                else {\n                    find_nacl(fd);\n                    close(fd);\n                    FD_CLR(fd, &readset);\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/freon.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * LD_PRELOAD hack to make Xorg happy in a system without VT-switching.\n * gcc -shared -fPIC -ldrm -ldl -I/usr/include/libdrm -Wall -O2 freon.c -o croutonfreon.so\n *\n * Powered by black magic.\n */\n\n#define _GNU_SOURCE\n#include <dlfcn.h>\n#include <stdio.h>\n#include <sys/file.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <string.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <linux/input.h>\n#include <linux/vt.h>\n#include <xf86drm.h>\n#include <xf86drmMode.h>\n\n#define LOCK_FILE_DIR \"/tmp/crouton-lock\"\n#define DISPLAY_LOCK_FILE LOCK_FILE_DIR \"/display\"\n#define LIBCROSSERVICE_METHOD_CALL(function) \\\n    system(\"host-dbus dbus-send --system --dest=org.chromium.LibCrosService \" \\\n           \"--type=method_call --print-reply /org/chromium/LibCrosService \" \\\n           \"org.chromium.LibCrosServiceInterface.\" #function)\n#define DISPLAYSERVICE_METHOD_CALL(function) \\\n    system(\"host-dbus dbus-send --system --dest=org.chromium.DisplayService \" \\\n           \"--type=method_call --print-reply /org/chromium/DisplayService \" \\\n           \"org.chromium.DisplayServiceInterface.\" #function)\n\n#define TRACE(...) /* fprintf(stderr, __VA_ARGS__) */\n#define ERROR(...) fprintf(stderr, __VA_ARGS__)\n\nstatic int tty0fd = -1;\nstatic int tty7fd = -1;\nstatic int lockfd = -1;\n\nstatic int (*orig_ioctl)(int d, int request, void* data);\nstatic int (*orig_open)(const char *pathname, int flags, mode_t mode);\nstatic int (*orig_open64)(const char *pathname, int flags, mode_t mode);\nstatic int (*orig_close)(int fd);\n\nstatic void preload_init() {\n    orig_ioctl = dlsym(RTLD_NEXT, \"ioctl\");\n    orig_open = dlsym(RTLD_NEXT, \"open\");\n    orig_open64 = dlsym(RTLD_NEXT, \"open64\");\n    orig_close = dlsym(RTLD_NEXT, \"close\");\n}\n\n/* Grabs the system-wide lockfile that arbitrates which chroot is using the GPU.\n *\n * pid should be either the pid of the process that owns the GPU (eg. getpid()),\n * or 0 to indicate that Chromium OS now owns the GPU.\n *\n * Returns 0 on success, or -1 on error.\n */\nstatic int set_display_lock(unsigned int pid) {\n    if (lockfd == -1) {\n        if (pid == 0) {\n            ERROR(\"No display lock to release.\\n\");\n            return 0;\n        }\n        lockfd = orig_open(DISPLAY_LOCK_FILE, O_CREAT | O_WRONLY, 0666);\n        if (lockfd == -1) {\n            ERROR(\"Unable to open display lock file.\\n\");\n            return -1;\n        }\n        if (flock(lockfd, LOCK_EX) == -1) {\n            ERROR(\"Unable to lock display lock file.\\n\");\n            return -1;\n        }\n    }\n    if (ftruncate(lockfd, 0) == -1) {\n        ERROR(\"Unable to truncate display lock file.\\n\");\n        return -1;\n    }\n    char buf[11];\n    int len;\n    if ((len = snprintf(buf, sizeof(buf), \"%u\\n\", pid)) < 0) {\n        ERROR(\"pid sprintf failed.\\n\");\n        return -1;\n    }\n    if (write(lockfd, buf, len) == -1) {\n        ERROR(\"Unable to write to display lock file.\\n\");\n        return -1;\n    }\n    if (pid == 0) {\n        int ret = orig_close(lockfd);\n        lockfd = -1;\n        if (ret == -1) {\n            ERROR(\"Failure when closing display lock file.\\n\");\n        }\n        return ret;\n    }\n    return 0;\n}\n\nstatic int32_t crtc_set_prop(int fd, uint32_t crtc_id,\n\t\t\t     drmModeObjectPropertiesPtr props,\n\t\t\t     const char *name, uint64_t value)\n{\n    uint32_t u;\n    int32_t ret;\n    drmModePropertyPtr prop;\n\n    for (u = 0; u < props->count_props; u++) {\n        prop = drmModeGetProperty(fd, props->props[u]);\n\tif (!prop)\n\t    continue;\n\tif (strcmp(prop->name, name)) {\n\t    drmModeFreeProperty(prop);\n\t    continue;\n\t}\n\tret = drmModeObjectSetProperty(fd, crtc_id, DRM_MODE_OBJECT_CRTC,\n                                       props->props[u], value);\n\tif (ret < 0)\n\t    TRACE(\"setting property %s failed with %d\\n\", name, ret);\n\telse\n\t    ret = 0;\n\tdrmModeFreeProperty(prop);\n\treturn ret;\n    }\n    TRACE(\"could not find property %s\\n\", name);\n    return -1;\n}\n\n/* Reset CTM/GAMMA properties to avoid artifacts (#3791). */\nstatic void drm_reset_props()\n{\n    int i, fd;\n    drmModeRes* resources;\n\n    if (!orig_open) preload_init();\n\n    fd = orig_open(\"/dev/dri/card0\", O_RDWR, 0);\n\n    TRACE(\"%s %d\\n\", __func__, fd);\n\n    if (fd < 0)\n        return;\n\n    resources = drmModeGetResources(fd);\n    TRACE(\"%s res=%p\\n\", __func__, resources);\n    if (!resources)\n        goto closefd;\n\n    for (i = 0; i < resources->count_crtcs; i++) {\n        drmModeObjectPropertiesPtr crtc_props;\n\tuint32_t crtc_id = resources->crtcs[i];\n\n\tcrtc_props = drmModeObjectGetProperties(fd, crtc_id, DRM_MODE_OBJECT_CRTC);\n\tTRACE(\"%s crtc %d %p\\n\", __func__, i, crtc_props);\n\tif (!crtc_props)\n            continue;\n\n        /* Reset color matrix to identity and gamma/degamma LUTs to pass through,\n         * ignore errors in case they are not supported.\n         * ref: https://chromium.googlesource.com/chromiumos/platform/frecon/+/master/drm.c\n         */\n        crtc_set_prop(fd, crtc_id, crtc_props, \"CTM\", 0);\n        crtc_set_prop(fd, crtc_id, crtc_props, \"DEGAMMA_LUT\", 0);\n        crtc_set_prop(fd, crtc_id, crtc_props, \"GAMMA_LUT\", 0);\n\n        drmModeFreeObjectProperties(crtc_props);\n    }\n\n    drmModeFreeResources(resources);\n\nclosefd:\n    orig_close(fd);\n}\n\n/* Prevents some glitch if Chromium OS keeps cursor enabled (#2878). */\nstatic void drm_disable_cursor()\n{\n    int i, fd;\n    drmModeRes* resources;\n\n    if (!orig_open) preload_init();\n\n    fd = orig_open(\"/dev/dri/card0\", O_RDWR, 0);\n\n    TRACE(\"%s %d\\n\", __func__, fd);\n\n    if (fd < 0)\n        return;\n\n    resources = drmModeGetResources(fd);\n    if (!resources)\n        goto closefd;\n\n    TRACE(\"%s res=%p\\n\", __func__, resources);\n    for (i = 0; i < resources->count_crtcs; i++) {\n        drmModeCrtc* crtc;\n\n        crtc = drmModeGetCrtc(fd, resources->crtcs[i]);\n        TRACE(\"%s crtc %d %p\\n\", __func__, i, crtc);\n        if (crtc) {\n            drmModeSetCursor(fd, crtc->crtc_id,\n                             0, 0, 0);\n            drmModeFreeCrtc(crtc);\n        }\n    }\n\n    drmModeFreeResources(resources);\n\nclosefd:\n    orig_close(fd);\n}\n\nint ioctl(int fd, unsigned long int request, ...) {\n    if (!orig_ioctl) preload_init();\n\n    int ret = 0;\n    va_list argp;\n    va_start(argp, request);\n    void* data = va_arg(argp, void*);\n\n    if (fd == tty0fd) {\n        TRACE(\"ioctl tty0 %d %lx %p\\n\", fd, request, data);\n        if (request == VT_OPENQRY) {\n            TRACE(\"OPEN\\n\");\n            *(int*)data = 7;\n        }\n        ret = 0;\n    } else if (fd == tty7fd) {\n        TRACE(\"ioctl tty7 %d %lx %p\\n\", fd, request, data);\n        if (request == VT_GETSTATE) {\n            TRACE(\"STATE\\n\");\n            struct vt_stat* stat = data;\n            stat->v_active = 0;\n        }\n\n        if ((request == VT_RELDISP && (uintptr_t)data == 1) ||\n            (request == VT_ACTIVATE && (uintptr_t)data == 0)) {\n            if (lockfd != -1) {\n                drm_reset_props();\n                TRACE(\"Telling Chromium OS to regain control\\n\");\n                ret = LIBCROSSERVICE_METHOD_CALL(TakeDisplayOwnership);\n                if (WEXITSTATUS(ret) == 1) {\n                    ret = DISPLAYSERVICE_METHOD_CALL(TakeOwnership);\n                }\n                if (set_display_lock(0) < 0) {\n                    ERROR(\"Failed to release display lock\\n\");\n                }\n            }\n        } else if ((request == VT_RELDISP && (uintptr_t)data == 2) ||\n                   (request == VT_ACTIVATE && (uintptr_t)data == 7)) {\n            if (set_display_lock(getpid()) == 0) {\n                TRACE(\"Telling Chromium OS to drop control\\n\");\n                ret = LIBCROSSERVICE_METHOD_CALL(ReleaseDisplayOwnership);\n                if (WEXITSTATUS(ret) == 1) {\n                    ret = DISPLAYSERVICE_METHOD_CALL(ReleaseOwnership);\n                }\n            } else {\n                ERROR(\"Unable to claim display lock\\n\");\n                ret = -1;\n            }\n            drm_disable_cursor();\n        } else {\n            ret = 0;\n        }\n    } else {\n        if (request == EVIOCGRAB) {\n            TRACE(\"ioctl GRAB %d %lx %p\\n\", fd, request, data);\n            /* Driver requested a grab: assume we have it already and report\n             * success */\n            ret = 0;\n        } else {\n            ret = orig_ioctl(fd, request, data);\n        }\n    }\n    va_end(argp);\n    return ret;\n}\n\nstatic int _open(int (*origfunc)(const char *pathname, int flags, mode_t mode),\n                 const char *origname, const char *pathname, int flags, mode_t mode) {\n    TRACE(\"%s %s\\n\", origname, pathname);\n    if (!strcmp(pathname, \"/dev/tty0\")) {\n        tty0fd = origfunc(\"/dev/null\", flags, mode);\n        return tty0fd;\n    } else if (!strcmp(pathname, \"/dev/tty7\")) {\n        tty7fd = origfunc(\"/dev/null\", flags, mode);\n        return tty7fd;\n    } else {\n        const char* event = \"/dev/input/event\";\n        int fd = origfunc(pathname, flags, mode);\n        TRACE(\"%s %s %d\\n\", origname, pathname, fd);\n        if (!strncmp(pathname, event, strlen(event))) {\n            TRACE(\"GRAB\\n\");\n            orig_ioctl(fd, EVIOCGRAB, (void *) 1);\n        }\n        return fd;\n    }\n}\n\nint open(const char *pathname, int flags, ...) {\n    if (!orig_open) preload_init();\n\n    va_list argp;\n    va_start(argp, flags);\n    mode_t mode = va_arg(argp, mode_t);\n    va_end(argp);\n\n    return _open(orig_open, \"open\", pathname, flags, mode);\n}\n\nint open64(const char *pathname, int flags, ...) {\n    if (!orig_open64) preload_init();\n\n    va_list argp;\n    va_start(argp, flags);\n    mode_t mode = va_arg(argp, mode_t);\n    va_end(argp);\n\n    return _open(orig_open64, \"open64\", pathname, flags, mode);\n}\n\nint close(int fd) {\n    if (!orig_close) preload_init();\n\n    TRACE(\"close %d\\n\", fd);\n\n    if (fd == tty0fd) {\n        tty0fd = -1;\n    } else if (fd == tty7fd) {\n        tty7fd = -1;\n    }\n    return orig_close(fd);\n}\n\nuid_t getuid0(void) {\n    TRACE(\"getuid0\\n\");\n    return 0;\n}\n"
  },
  {
    "path": "src/vtmonitor.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Monitors changes in virtual terminal (VT). This is done by opening\n * /sys/class/tty/tty0/active, and waiting for POLLPRI event. Then, we\n * seek to the beginning of the file, read its content (which looks\n * like ttyX), and start polling again.\n */\n\n#include <poll.h>\n#include <string.h>\n#include <stdio.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#define SYSFILE \"/sys/class/tty/tty0/active\"\n\nint main(int argc, char **argv) {\n    int fd;\n    struct pollfd fds[1];\n    char buffer[16];\n\n    fd = open(SYSFILE, O_RDONLY);\n\n    if (fd < 0) {\n        perror(\"Cannot open \" SYSFILE);\n        return 1;\n    }\n\n    memset(fds, 0, sizeof(fds));\n    fds[0].fd = fd;\n    fds[0].events = POLLPRI;\n\n    while (1) {\n        /* Wait for events */\n        int n = poll(fds, 1, -1);\n        if (n <= 0) {\n            perror(\"poll error.\");\n            return 1;\n        }\n\n        if (fds[0].revents & POLLPRI) {\n            /* Seek back to beginning of file and read the tty number. */\n            lseek(fd, 0, SEEK_SET);\n            n = read(fd, buffer, 16);\n            if (n <= 0) {\n                perror(\"Cannot read from \" SYSFILE \" file.\");\n                return 1;\n            }\n\n            /* Write tty number to stdout */\n            fwrite(buffer, n, 1, stdout);\n            fflush(stdout);\n        } else {\n            fprintf(stderr, \"Unknown poll event.\\n\");\n            return 1;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/websocket.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * WebSocket server that provides an interface to an extension running in\n * Chromium OS, used for clipboard synchronization and URL handling.\n *\n */\n\n#include \"websocket.h\"\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <netinet/tcp.h>\n\n/* WebSocket constants */\n#define VERSION \"V2\"\n#define PORT 30001\n\n/* Pipe constants */\nconst char* PIPE_DIR = \"/tmp/crouton-ext\";\nconst char* PIPEIN_FILENAME = \"/tmp/crouton-ext/in\";\nconst char* PIPEOUT_FILENAME = \"/tmp/crouton-ext/out\";\nconst char* PIPE_VERSION_FILE = \"/tmp/crouton-ext/version\";\nconst int PIPEOUT_WRITE_TIMEOUT = 3000;\n\n/* File descriptors */\nstatic int pipein_fd = -1;\nstatic int pipeout_fd = -1;\n\nstatic void pipeout_close();\nstatic int socket_client_handle_unrequested(const char* buffer,\n                                            const int length);\n\n/* Open a pipe in non-blocking mode, then set it back to blocking mode. */\n/* Returns fd on success, -1 if the pipe cannot be open, -2 if the O_NONBLOCK\n * flag cannot be cleared. */\nstatic int pipe_open_block(const char* path, int oflag) {\n    int fd;\n\n    log(3, \"%s\", path);\n\n    fd = open(path, oflag | O_NONBLOCK);\n    if (fd < 0)\n        return -1;\n\n    /* Remove non-blocking flag */\n    int flags = fcntl(fd, F_GETFL, 0);\n    if (flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) {\n        syserror(\"error in fnctl GETFL/SETFL.\");\n        close(fd);\n        return -2;\n    }\n\n    return fd;\n}\n\n/**/\n/* Pipe out functions */\n/**/\n\n/* Open the pipe out. Returns 0 on success, -1 on error. */\nstatic int pipeout_open() {\n    int i;\n\n    log(2, \"Opening pipe out...\");\n\n    /* Unfortunately, in the case where no reader is available (yet), opening\n     * pipes for writing behaves as follows: In blocking mode, \"open\" blocks.\n     * In non-blocking mode, it fails (returns -1). This means that we cannot\n     * open the pipe, then use functions like poll/select to detect when a\n     * reader becomes available. Waiting forever is also not an option: we do\n     * want to block this server if a client \"forgets\" to read the answer back.\n     * Therefore, we are forced to poll manually.\n     * Using usleep is simpler, and probably better than measuring time elapsed:\n     * If the system hangs for a while (like during large I/O writes), this will\n     * still wait around PIPEOUT_WRITE_TIMEOUT ms of actual user time, instead\n     * of clock time. */\n    for (i = 0; i < PIPEOUT_WRITE_TIMEOUT/10; i++) {\n        pipeout_fd = pipe_open_block(PIPEOUT_FILENAME, O_WRONLY);\n        if (pipeout_fd >= 0)\n            break;\n        if (pipeout_fd == -2) /* fnctl error: this is fatal. */\n            exit(1);\n        usleep(10000);\n    }\n\n    if (pipeout_fd < 0) {\n        error(\"Timeout while opening.\");\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic void pipeout_close() {\n    log(2, \"Closing...\");\n\n    if (pipeout_fd < 0)\n        return;\n\n    close(pipeout_fd);\n    pipeout_fd = -1;\n}\n\nstatic int pipeout_write(char* buffer, int len) {\n    int n;\n\n    log(3, \"(fd=%d, len=%d)\", pipeout_fd, len);\n\n    if (pipeout_fd < 0)\n        return -1;\n\n    n = block_write(pipeout_fd, buffer, len);\n    if (n != len) {\n        error(\"Error writing to pipe.\");\n        pipeout_close();\n    }\n    return n;\n}\n\n/* Open pipe out, write a string, then close the pipe. */\nstatic void pipeout_error(char* str) {\n    pipeout_open();\n    pipeout_write(str, strlen(str));\n    pipeout_close();\n}\n\n/**/\n/* Pipe in functions */\n/**/\n\n/* Flush the pipe (in case of error), close it, then reopen it. Reopening is\n * necessary to prevent poll from getting continuous POLLHUP when the process\n * that writes into the pipe terminates (croutonurlhandler for example).\n * This MUST be called before anything is written to pipeout to avoid race\n * condition, where we would flush out legitimate data from a second process */\nstatic void pipein_reopen() {\n    if (pipein_fd >= 0) {\n        char buffer[BUFFERSIZE];\n        while (read(pipein_fd, buffer, BUFFERSIZE) > 0);\n        close(pipein_fd);\n    }\n\n    pipein_fd = pipe_open_block(PIPEIN_FILENAME, O_RDONLY);\n    if (pipein_fd < 0) {\n        syserror(\"Cannot open pipe in.\");\n        exit(1);\n    }\n}\n\n/* Read data from the pipe, and forward it to the socket client. */\nstatic void pipein_read() {\n    int n;\n    char buffer[FRAMEMAXHEADERSIZE+BUFFERSIZE];\n    int first = 1;\n    char firstchar = '\\0';\n\n    if (client_fd < 0) {\n        log(1, \"No client FD.\");\n        pipein_reopen();\n        pipeout_error(\"EError: not connected.\");\n        return;\n    }\n\n    while (1) {\n        n = read(pipein_fd, buffer+FRAMEMAXHEADERSIZE, BUFFERSIZE);\n        log(3, \"n=%d\", n);\n\n        if (n < 0) {\n            /* This is very unlikely, and fatal. */\n            syserror(\"Error reading from pipe.\");\n            exit(1);\n        } else if (n == 0) {\n            break;\n        }\n\n        if (first)\n            firstchar = buffer[FRAMEMAXHEADERSIZE];\n\n        /* Write a text frame for the first packet, then cont frames. */\n        n = socket_client_write_frame(buffer, n,\n                                  first ? WS_OPCODE_TEXT : WS_OPCODE_CONT, 0);\n        if (n < 0) {\n            error(\"Error writing frame.\");\n            pipein_reopen();\n            pipeout_error(\"EError: socket write error.\");\n            return;\n        }\n\n        first = 0;\n    }\n\n    log(3, \"EOF\");\n\n    pipein_reopen();\n\n    /* Empty FIN frame to finish the message. */\n    n = socket_client_write_frame(buffer, 0,\n                                  first ? WS_OPCODE_TEXT : WS_OPCODE_CONT, 1);\n    if (n < 0) {\n        error(\"Error writing frame.\");\n        pipeout_error(\"EError: socket write error\");\n        return;\n    }\n\n    log(2, \"Reading answer from client...\");\n\n    int fin = 0;\n    uint32_t maskkey;\n    int retry = 0;\n    first = 1;\n\n    /* Ignore return value, so we still read the frame even if pipeout\n     * cannot be open. */\n    pipeout_open();\n\n    /* Read possibly fragmented message from WebSocket. */\n    while (fin != 1) {\n        int len = socket_client_read_frame_header(&fin, &maskkey, &retry);\n\n        log(3, \"len=%d fin=%d retry=%d...\", len, fin, retry);\n\n        if (retry)\n            continue;\n\n        if (len < 0)\n            goto exit;\n\n        /* Read the whole frame, and write it to pipeout */\n        while (len > 0) {\n            int rlen = (len > BUFFERSIZE) ? BUFFERSIZE: len;\n            if (socket_client_read_frame_data(buffer, rlen, maskkey) < 0)\n                goto exit;\n\n            /* Check first byte */\n            if (first && buffer[0] != firstchar && buffer[0] != 'E') {\n                /* This is not a response: unrequested packet */\n                if (!fin && len < BUFFERSIZE) {\n                    /* !fin, and buffer not full, finish reading... */\n                    rlen = socket_client_read_frame(buffer+len,\n                                                    sizeof(buffer)-len);\n                    if (rlen < 0)\n                        goto exit;\n\n                    len += rlen;\n                }\n\n                if (len >= BUFFERSIZE) {\n                    error(\"Unrequested command too long: (>%d bytes).\", len);\n                    socket_client_close(1);\n                    goto exit;\n                }\n\n                if (socket_client_handle_unrequested(buffer, len) < 0)\n                    goto exit;\n\n                /* Command was handled, try reading the answer again. */\n                fin = 0;\n                break;\n            }\n\n            /* Ignore return value as well */\n            pipeout_write(buffer, rlen);\n            len -= rlen;\n            first = 0;\n        }\n    }\n\nexit:\n    pipeout_close();\n}\n\n/* Check if filename is a valid FIFO pipe. If not create it.\n * Returns 0 on success, -1 on error. */\nint checkfifo(const char* filename) {\n    struct stat fstat;\n\n    /* Check if file exists: if not, create the FIFO. */\n    if (access(filename, F_OK) < 0) {\n        if (mkfifo(filename,\n                S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) < 0) {\n            syserror(\"Cannot create FIFO pipe.\");\n            return -1;\n        }\n        return 0;\n    }\n\n    /* We must be able to read and write the file. (only one direction is\n     * necessary in croutonwebsocket, but croutonclip needs the other direction)\n     */\n    if (access(filename, R_OK|W_OK) < 0) {\n        error(\"%s exists, but not readable and writable.\",\n                filename);\n        return -1;\n    }\n\n    if (stat(filename, &fstat) < 0) {\n        syserror(\"Cannot stat FIFO pipe.\");\n        return -1;\n    }\n\n    if (!S_ISFIFO(fstat.st_mode)) {\n        error(\"%s exists, but is not a FIFO pipe.\", filename);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Initialise FIFO pipes. */\nvoid pipe_init() {\n    struct stat fstat;\n\n    /* Check if directory exists: if not, create it. */\n    if (access(PIPE_DIR, F_OK) < 0) {\n        if (mkdir(PIPE_DIR, S_IRWXU|S_IRWXG|S_IRWXO) < 0) {\n            syserror(\"Cannot create FIFO pipe directory.\");\n            exit(1);\n        }\n    } else {\n        if (stat(PIPE_DIR, &fstat) < 0) {\n            syserror(\"Cannot stat FIFO pipe directory.\");\n            exit(1);\n        }\n\n        if (!S_ISDIR(fstat.st_mode)) {\n            error(\"%s exists, but is not a directory.\", PIPE_DIR);\n            exit(1);\n        }\n    }\n\n    if (checkfifo(PIPEIN_FILENAME) < 0 ||\n        checkfifo(PIPEOUT_FILENAME) < 0) {\n        /* checkfifo prints an error already. */\n        exit(1);\n    }\n\n    /* Create a file with the version number of the protocol */\n    FILE *vers = fopen(PIPE_VERSION_FILE, \"w\");\n    if (!vers\n            || fputs(VERSION \"\\n\", vers) == EOF\n            || fclose(vers) == EOF) {\n        error(\"Unable to write to %s.\", PIPE_VERSION_FILE);\n        exit(1);\n    }\n\n    pipein_reopen();\n}\n\n/* Handle unrequested packet from extension.\n * Returns 0 on success. On error, returns -1 and closes websocket connection.\n */\nstatic int socket_client_handle_unrequested(const char* buffer,\n                                            const int length) {\n    /* Process the client request. */\n    switch (buffer[0]) {\n        case 'C': {  /* Send a command to croutoncycle */\n            char reply[BUFFERSIZE];\n            int replylength = 1;\n            reply[FRAMEMAXHEADERSIZE] = 'C';\n\n            char* cmd = \"croutoncycle\";\n            char param[length];\n            memcpy(param, buffer+1, length-1);\n            param[length-1] = '\\0';\n            char* args[] = { cmd, param, NULL };\n\n            log(2, \"Received croutoncycle command (%s)\", param);\n\n            /* We are only interested in the output for list commands */\n            if (param[0] == 'l') {\n                int n = popen2(cmd, args, NULL, 0,\n                               &reply[FRAMEMAXHEADERSIZE+1],\n                               BUFFERSIZE-FRAMEMAXHEADERSIZE-1);\n                if (n < 0) {\n                    error(\"Call to croutoncycle failed.\");\n                    socket_client_close(0);\n                    return -1;\n                }\n                replylength += n;\n            } else if (param[0] == 'O') {\n                /* Extra OK response from a C back-and-forth. Disregard. */\n                break;\n            } else {\n                /* Launch command in background (this is necessary as\n                   croutoncycle may send a websocket command, leaving us\n                   deadlocked...) */\n                pid_t pid = fork();\n                if (pid < 0) {\n                    syserror(\"Fork error.\");\n                    exit(1);\n                } else if (pid == 0) {\n                    /* Double-fork to avoid zombies */\n                    pid_t pid2 = fork();\n                    if (pid2 < 0) {\n                        syserror(\"Fork error.\");\n                        exit(1);\n                    } else if (pid2 == 0) {\n                        execvp(cmd, args);\n                        error(\"Error running '%s'.\", cmd);\n                        exit(127);\n                    }\n                    exit(0);\n                }\n                /* Wait for first fork to complete. */\n                waitpid(pid, NULL, 0);\n            }\n            if (socket_client_write_frame(reply, replylength,\n                                          WS_OPCODE_TEXT, 1) < 0) {\n                error(\"Write error.\");\n                socket_client_close(0);\n                return -1;\n            }\n            break;\n        }\n        default: {\n            int len = length > 64 ? 64 : length;\n            char dump[len+1];\n            memcpy(dump, buffer, len);\n            dump[len] = '\\0';\n            error(\"Received an unexpected packet from client (%s).\", dump);\n            socket_client_close(0);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n/* Unrequested data came in from WebSocket client. */\nstatic void socket_client_read() {\n    char buffer[BUFFERSIZE];\n    int length;\n\n    length = socket_client_read_frame(buffer, sizeof(buffer));\n    if (length < 0) {\n        socket_client_close(1);\n        return;\n    }\n\n    if (length >= BUFFERSIZE) {\n        error(\"Unrequested command too long: (>%d bytes).\", length);\n        socket_client_close(1);\n        return;\n    }\n\n    /* Ignore return value (connection gets closed on error) */\n    socket_client_handle_unrequested(buffer, length);\n}\n\nstatic int terminate = 0;\n\nstatic void signal_handler(int sig) {\n    terminate = 1;\n}\n\nint main(int argc, char **argv) {\n    int n;\n    /* Poll array:\n     * 0 - server_fd\n     * 1 - pipein_fd\n     * 2 - client_fd (if any)\n     */\n    struct pollfd fds[3];\n    int nfds = 3;\n    sigset_t sigmask;\n    sigset_t sigmask_orig;\n    struct sigaction act;\n    int c;\n\n    while ((c = getopt(argc, argv, \"v:\")) != -1) {\n        switch (c) {\n        case 'v':\n            verbose = atoi(optarg);\n            break;\n        default:\n            fprintf(stderr, \"%s [-v 0-3]\\n\", argv[0]);\n            return 1;\n        }\n    }\n\n    /* Termination signal handler. */\n    memset(&act, 0, sizeof(act));\n    act.sa_handler = signal_handler;\n\n    if (sigaction(SIGHUP, &act, 0) < 0 ||\n        sigaction(SIGINT, &act, 0) < 0 ||\n        sigaction(SIGTERM, &act, 0) < 0) {\n        syserror(\"sigaction error.\");\n        return 2;\n    }\n\n    /* Ignore SIGPIPE in all cases: it may happen, since we write to pipes, but\n     * it is not fatal. */\n    sigemptyset(&sigmask);\n    sigaddset(&sigmask, SIGPIPE);\n\n    if (sigprocmask(SIG_BLOCK, &sigmask, NULL) < 0) {\n        syserror(\"sigprocmask error.\");\n        return 2;\n    }\n\n    /* Ignore terminating signals, except when ppoll is running. Save current\n     * mask in sigmask_orig. */\n    sigemptyset(&sigmask);\n    sigaddset(&sigmask, SIGHUP);\n    sigaddset(&sigmask, SIGINT);\n    sigaddset(&sigmask, SIGTERM);\n\n    if (sigprocmask(SIG_BLOCK, &sigmask, &sigmask_orig) < 0) {\n        syserror(\"sigprocmask error.\");\n        return 2;\n    }\n\n    /* Prepare pollfd structure. */\n    memset(fds, 0, sizeof(fds));\n    fds[0].events = POLLIN;\n    fds[1].events = POLLIN;\n    fds[2].events = POLLIN;\n\n    /* Initialise pipe and WebSocket server */\n    socket_server_init(PORT);\n    pipe_init();\n\n    while (!terminate) {\n        /* Make sure fds is up to date. */\n        fds[0].fd = server_fd;\n        fds[1].fd = pipein_fd;\n        fds[2].fd = client_fd;\n\n        /* Only handle signals in ppoll: this makes sure we complete processing\n         * the current request before bailing out. */\n        n = ppoll(fds, nfds, NULL, &sigmask_orig);\n\n        log(3, \"poll ret=%d (%d, %d, %d)\\n\", n,\n                   fds[0].revents, fds[1].revents, fds[2].revents);\n\n        if (n < 0) {\n            /* Do not print error when ppoll is interupted by a signal. */\n            if (errno != EINTR || verbose >= 1)\n                syserror(\"ppoll error.\");\n            break;\n        }\n\n        if (fds[0].revents & POLLIN) {\n            log(1, \"WebSocket accept.\");\n            socket_server_accept(VERSION);\n            n--;\n        }\n        if (fds[1].revents & POLLIN) {\n            log(2, \"Pipe fd ready.\");\n            pipein_read();\n            n--;\n        }\n        if (fds[2].revents & POLLIN) {\n            log(2, \"Client fd ready.\");\n            socket_client_read();\n            n--;\n        }\n\n        if (n > 0) { /* Some events were not handled, this is a problem */\n            error(\"Some poll events could not be handled: \"\n                    \"ret=%d (%d, %d, %d).\",\n                    n, fds[0].revents, fds[1].revents, fds[2].revents);\n            break;\n        }\n    }\n\n    log(1, \"Terminating...\");\n\n    if (client_fd)\n        socket_client_close(1);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/websocket.h",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Provides common WebSocket functions that can be used by both websocket.c\n * and fbserver.c.\n *\n * Mostly compliant with RFC 6455 - The WebSocket Protocol.\n *\n * Things that are supported, but not tested:\n *  - Fragmented packets from client\n *  - Ping packets\n */\n\n#ifndef WEBSOCKET_H_\n#define WEBSOCKET_H_\n\n#define _GNU_SOURCE /* for ppoll */\n#include <ctype.h>\n#include <errno.h>\n#include <poll.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <netinet/in.h>\n#include <sys/wait.h>\n\nconst int BUFFERSIZE = 4096;\n\n/* WebSocket constants */\nconst int FRAMEMAXHEADERSIZE = 16; // Actually 2+8, but align on 8-byte boundary\nconst int MAXFRAMESIZE = 16*1048576; // 16MiB\nconst char* GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n/* Key from client must be 24 bytes long (16 bytes, base64 encoded) */\nconst int SECKEY_LEN = 24;\n/* SHA-1 is 20 bytes long */\nconst int SHA1_LEN = 20;\n/* base64-encoded SHA-1 must be 28 bytes long (ceil(20/3*4)+1). */\nconst int SHA1_BASE64_LEN = 28;\n\n/* WebSocket opcodes */\nconst int WS_OPCODE_CONT = 0x0;\nconst int WS_OPCODE_TEXT = 0x1;\nconst int WS_OPCODE_BINARY = 0x2;\nconst int WS_OPCODE_CLOSE = 0x8;\nconst int WS_OPCODE_PING = 0x9;\nconst int WS_OPCODE_PONG = 0xA;\n\n/* WebSocket bitmasks */\nconst char WS_HEADER0_FIN = 0x80;  /* fin */\nconst char WS_HEADER0_RSV = 0x70;  /* reserved */\nconst char WS_HEADER0_OPCODE_MASK = 0x0F;  /* opcode */\nconst char WS_HEADER1_MASK = 0x80;  /* mask */\nconst char WS_HEADER1_LEN_MASK = 0x7F;  /* payload length */\n\n/* 0 - Quiet\n * 1 - General messages (init, new connections)\n * 2 - 1 + Information on each transfer\n * 3 - 2 + Extra information */\nstatic int verbose = 0;\n\n#define log(level, str, ...) do { \\\n    if (verbose >= (level)) printf(\"%s: \" str \"\\n\", __func__, ##__VA_ARGS__); \\\n} while (0)\n\n#define error(str, ...) printf(\"%s: \" str \"\\n\", __func__, ##__VA_ARGS__)\n\n/* Aborts if expr is false */\n#define trueorabort(expr, str, ...) do { \\\n    if (!(expr)) { \\\n        printf(\"%s: ASSERTION \" #expr \" FAILED (\" str \")\\n\", \\\n               __func__, ##__VA_ARGS__);                     \\\n        abort(); \\\n    }            \\\n} while (0)\n\n/* Similar to perror, but prints function name as well */\n#define syserror(str, ...) printf(\"%s: \" str \" (%s)\\n\", \\\n                    __func__, ##__VA_ARGS__, strerror(errno))\n\n/* Port number, assigned in socket_server_init() */\nstatic int port = -1;\n\n/* File descriptors */\nstatic int server_fd = -1;\nstatic int client_fd = -1;\n\n/* Prototypes */\nstatic int socket_client_write_frame(char* buffer, unsigned int size,\n                                     unsigned int opcode, int fin);\nstatic int socket_client_read_frame_header(int* fin, uint32_t* maskkey,\n                                           int* length);\nstatic int socket_client_read_frame_data(char* buffer, unsigned int size,\n                                         uint32_t maskkey);\nstatic void socket_client_close(int close_reason);\n\n/**/\n/* Helper functions */\n/**/\n\n/* Read exactly size bytes from fd, no matter how many reads it takes.\n * Returns size if successful, < 0 in case of error. */\nstatic int block_read(int fd, char* buffer, size_t size) {\n    int n;\n    int tot = 0;\n\n    while (tot < size) {\n        n = read(fd, buffer + tot, size - tot);\n        log(3, \"n=%d+%d/%zd\", n, tot, size);\n        if (n < 0)\n            return n;\n        if (n == 0)\n            return -1;  /* EOF */\n        tot += n;\n    }\n\n    return tot;\n}\n\n/* Write exactly size bytes from fd, no matter how many writes it takes.\n * Returns size if successful, < 0 in case of error. */\nstatic int block_write(int fd, char* buffer, size_t size) {\n    int n;\n    int tot = 0;\n\n    while (tot < size) {\n        n = write(fd, buffer + tot, size - tot);\n        log(3, \"n=%d+%d/%zd\", n, tot, size);\n        if (n < 0)\n            return n;\n        if (n == 0)\n            return -1;\n        tot += n;\n    }\n\n    return tot;\n}\n\n/* Run external command, piping some data on its stdin, and reading back\n * the output. Returns the number of bytes read from the process (at most\n * outlen), or a negative number on error (-exit status). */\nstatic int popen2(char* cmd, char *const argv[],\n                  char* input, int inlen, char* output, int outlen) {\n    pid_t pid = 0;\n    int stdin_fd[2];\n    int stdout_fd[2];\n\n    if (pipe(stdin_fd) < 0 || pipe(stdout_fd) < 0) {\n        syserror(\"Failed to create pipe.\");\n        return -1;\n    }\n\n    log(3, \"pipes: in %d/%d; out %d/%d\",\n           stdin_fd[0], stdin_fd[1], stdout_fd[0], stdout_fd[1]);\n\n    pid = fork();\n\n    if (pid < 0) {\n        syserror(\"Fork error.\");\n        return -1;\n    } else if (pid == 0) {\n        /* Child: connect stdin/out to the pipes, close the unneeded halves */\n        close(stdin_fd[1]);\n        dup2(stdin_fd[0], STDIN_FILENO);\n        close(stdout_fd[0]);\n        dup2(stdout_fd[1], STDOUT_FILENO);\n\n        if (argv) {\n            execvp(cmd, argv);\n        } else {\n            execlp(cmd, cmd, NULL);\n        }\n\n        syserror(\"Error running '%s'.\", cmd);\n        exit(127);\n    }\n\n    /* Parent */\n\n    /* Close uneeded halves (those are used by the child) */\n    close(stdin_fd[0]);\n    close(stdout_fd[1]);\n\n    /* Write input, and read output. We rely on POLLHUP getting set on stdout\n     * when the process exits (this assumes the process does not do anything\n     * strange like closing stdout and staying alive). */\n    struct pollfd fds[2];\n    fds[0].events = POLLIN;\n    fds[0].fd = stdout_fd[0];\n    fds[1].events = POLLOUT;\n    fds[1].fd = stdin_fd[1];\n\n    int readlen = 0; /* Also acts as return value */\n    int writelen = 0;\n    while (1) {\n        int polln = poll(fds, 2, -1);\n\n        if (polln < 0) {\n            syserror(\"poll error.\");\n            readlen = -1;\n            break;\n        }\n\n        log(3, \"poll=%d\", polln);\n\n        /* We can write something to stdin */\n        if (fds[1].revents & POLLOUT) {\n            if (inlen > writelen) {\n                int n = write(stdin_fd[1], input + writelen, inlen - writelen);\n                if (n < 0) {\n                    error(\"write error.\");\n                    readlen = -1;\n                    break;\n                }\n                log(3, \"write n=%d/%d\", n, inlen);\n                writelen += n;\n            }\n\n            if (writelen == inlen) {\n                /* Done writing: Only poll stdout from now on. */\n                close(stdin_fd[1]);\n                stdin_fd[1] = -1;\n                fds[1].fd = -1;\n            }\n            fds[1].revents &= ~POLLOUT;\n        }\n\n        if (fds[1].revents != 0) {\n            error(\"Unknown poll event on stdout (%d).\", fds[1].revents);\n            readlen = -1;\n            break;\n        }\n\n        /* We can read something from stdout */\n        if (fds[0].revents & POLLIN) {\n            int n = read(stdout_fd[0], output + readlen, outlen - readlen);\n            if (n < 0) {\n                error(\"read error.\");\n                readlen = -1;\n                break;\n            }\n            log(3, \"read n=%d\", n);\n            readlen += n;\n\n            if (verbose >= 3) {\n                fwrite(output, 1, readlen, stdout);\n            }\n\n            if (readlen >= outlen) {\n                error(\"Output too long.\");\n                break;\n            }\n            fds[0].revents &= ~POLLIN;\n        }\n\n        /* stdout has hung up (process terminated) */\n        if (fds[0].revents == POLLHUP) {\n            log(3, \"pollhup\");\n            break;\n        } else if (fds[0].revents != 0) {\n            error(\"Unknown poll event on stdin (%d).\", fds[0].revents);\n            readlen = -1;\n            break;\n        }\n    }\n\n    if (stdin_fd[1] >= 0)\n        close(stdin_fd[1]);\n    /* Closing the stdout pipe forces the child process to exit */\n    close(stdout_fd[0]);\n\n    /* Get child status (no timeout: we assume the child behaves well) */\n    int status = 0;\n    pid_t wait_pid = waitpid(pid, &status, 0);\n\n    if (wait_pid != pid) {\n        syserror(\"waitpid error.\");\n        return -1;\n    }\n\n    if (WIFEXITED(status)) {\n        log(3, \"child exited!\");\n        if (WEXITSTATUS(status) != 0) {\n            error(\"child exited with status %d\", WEXITSTATUS(status));\n            return -WEXITSTATUS(status);\n        }\n    } else {\n        error(\"child process did not exit: %d\", status);\n        return -1;\n    }\n\n    if (writelen != inlen) {\n        error(\"Incomplete write.\");\n        return -1;\n    }\n\n    return readlen;\n}\n\n/**/\n/* Websocket functions. */\n/**/\n\n/* Close the client socket, sending a close packet if sendclose is true. */\nstatic void socket_client_close(int sendclose) {\n    if (client_fd < 0)\n        return;\n\n    if (sendclose) {\n        char buffer[FRAMEMAXHEADERSIZE];\n        socket_client_write_frame(buffer, 0, WS_OPCODE_CLOSE, 1);\n        /* FIXME: We are supposed to read back the answer (if we are not\n         * replying to a close frame sent by the client), but we probably do not\n         * want to block, waiting for the answer, so we just close the socket.\n         */\n    }\n\n    close(client_fd);\n    client_fd = -1;\n}\n\n/* Send a frame to the WebSocket client.\n *  - buffer needs to be FRAMEMAXHEADERSIZE+size long, and data must start at\n *    buffer[FRAMEMAXHEADERSIZE] only.\n *  - opcode should generally be WS_OPCODE_TEXT or WS_OPCODE_CONT (continuation)\n *  - fin indicates if the this is the last frame in the message\n * Returns size on success. On error, closes the socket, and returns -1.\n */\nstatic int socket_client_write_frame(char* buffer, unsigned int size,\n                                     unsigned int opcode, int fin) {\n    /* Start of frame, with header: at least 2 bytes before the actual data */\n    char* pbuffer = buffer + FRAMEMAXHEADERSIZE - 2;\n    int payloadlen = size;\n    int extlensize = 0;\n\n    /* Test if we need an extended length field. */\n    if (payloadlen > 125) {\n        if (payloadlen < 65536) {\n            payloadlen = 126;\n            extlensize = 2;\n        } else {\n            payloadlen = 127;\n            extlensize = 8;\n        }\n        pbuffer -= extlensize;\n\n        /* Network-order (big-endian) */\n        unsigned int tmpsize = size;\n        int i;\n        for (i = extlensize-1; i >= 0; i--) {\n            pbuffer[2+i] = tmpsize & 0xff;\n            tmpsize >>= 8;\n        }\n    }\n\n    pbuffer[0] = opcode & WS_HEADER0_OPCODE_MASK;\n    if (fin) pbuffer[0] |= WS_HEADER0_FIN;\n    pbuffer[1] = payloadlen;  /* No mask (0x80) in server->client direction */\n\n    int wlen = 2 + extlensize + size;\n    if (block_write(client_fd, pbuffer, wlen) != wlen) {\n        syserror(\"Write error.\");\n        socket_client_close(0);\n        return -1;\n    }\n\n    return size;\n}\n\n/* Read a WebSocket frame header:\n *  - fin indicates in this is the final frame in a fragmented message\n *  - maskkey is the XOR key used for the message\n *  - retry is set to 1 if we receive a control packet: the caller must call\n *    again if it expects more data.\n *\n * Returns the frame length on success. On error, closes the socket,\n * and returns -1.\n *\n * Data is then read with socket_client_read_frame_data()\n */\nstatic int socket_client_read_frame_header(int* fin, uint32_t* maskkey,\n                                           int* retry) {\n    char header[2]; /* Minimum header length */\n    char extlen[8]; /* Extended length */\n    int n;\n\n    *retry = 0;\n\n    n = block_read(client_fd, header, 2);\n    if (n != 2) {\n        error(\"Read error.\");\n        socket_client_close(0);\n        return -1;\n    }\n\n    int opcode, mask;\n    uint64_t length;\n    *fin = (header[0] & WS_HEADER0_FIN) != 0;\n    if (header[0] & WS_HEADER0_RSV) {\n        error(\"Reserved bits are on.\");\n        socket_client_close(1);\n        return -1;\n    }\n    opcode = header[0] & WS_HEADER0_OPCODE_MASK;\n    mask = (header[1] & WS_HEADER1_MASK) != 0;\n    length = header[1] & WS_HEADER1_LEN_MASK;\n\n    log(2, \"fin=%d; opcode=%d; mask=%d; length=%llu\",\n               *fin, opcode, mask, (long long unsigned int)length);\n\n    /* Read extended length if necessary */\n    int extlensize = 0;\n    if (length == 126)\n        extlensize = 2;\n    else if (length == 127)\n        extlensize = 8;\n\n    if (extlensize > 0) {\n        n = block_read(client_fd, extlen, extlensize);\n        if (n != extlensize) {\n            error(\"Read error.\");\n            socket_client_close(0);\n            return -1;\n        }\n\n        /* Network-order (big-endian) */\n        int i;\n        length = 0;\n        for (i = 0; i < extlensize; i++) {\n            length = length << 8 | (uint8_t)extlen[i];\n        }\n\n        log(3, \"extended length=%llu\", (long long unsigned int)length);\n    }\n\n    /* Read masking key if necessary */\n    if (mask) {\n        n = block_read(client_fd, (char*)maskkey, 4);\n        if (n != 4) {\n            error(\"Read error.\");\n            socket_client_close(0);\n            return -1;\n        }\n    } else {\n        /* RFC section 5.1 says we must close the connection if we receive a\n         * frame that is not masked. */\n        error(\"No mask set.\");\n        socket_client_close(1);\n        return -1;\n    }\n\n    log(3, \"maskkey=%04x\", *maskkey);\n\n    if (length > MAXFRAMESIZE) {\n        error(\"Frame too big! (%llu>%d)\\n\",\n                (long long unsigned int)length, MAXFRAMESIZE);\n        socket_client_close(1);\n        return -1;\n    }\n\n    /* is opcode continuation, text, or binary? */\n    /* FIXME: We should check that only the first packet is text or binary, and\n     * that the following are continuation ones. */\n    if (opcode != WS_OPCODE_CONT &&\n        opcode != WS_OPCODE_TEXT && opcode != WS_OPCODE_BINARY) {\n        log(2, \"Got a control packet (opcode=%d).\", opcode);\n\n        /* Control packets cannot be fragmented.\n         * Unknown data (opcodes 3-7) will result in error anyway. */\n        if (*fin == 0) {\n            error(\"Fragmented unknown packet (%x).\", opcode);\n            socket_client_close(1);\n            return -1;\n        }\n\n        /* Read the rest of the packet */\n        char* buffer = malloc(length+3); /* +3 for unmasking safety */\n        if (socket_client_read_frame_data(buffer, length, *maskkey) < 0) {\n            socket_client_close(0);\n            free(buffer);\n            return -1;\n        }\n\n        if (opcode == WS_OPCODE_CLOSE) {  /* Connection close. */\n            error(\"Connection close from WebSocket client.\");\n            socket_client_close(1);\n            free(buffer);\n            return -1;\n        } else if (opcode == WS_OPCODE_PING) {  /* Ping */\n            socket_client_write_frame(buffer, length, WS_OPCODE_PONG, 1);\n        } else if (opcode == WS_OPCODE_PONG) {  /* Pong */\n            /* Do nothing */\n        } else {  /* Unknown opcode */\n            error(\"Unknown packet (%x).\", opcode);\n            socket_client_close(1);\n            free(buffer);\n            return -1;\n        }\n\n        free(buffer);\n\n        /* Tell the caller to wait for the next packet */\n        *retry = 1;\n        *fin = 0;\n        return 0;\n    }\n\n    return length;\n}\n\n/* Read frame data from the WebSocket client:\n * - Make sure that buffer is at least 4*ceil(size/4) long, as unmasking works\n *   on blocks of 4 bytes.\n * Returns size on success (the buffer has been completely filled).\n * On error, closes the socket, and returns -1.\n */\nstatic int socket_client_read_frame_data(char* buffer, unsigned int size,\n                                         uint32_t maskkey) {\n    int n = block_read(client_fd, buffer, size);\n    if (n != size) {\n        error(\"Read error.\");\n        socket_client_close(0);\n        return -1;\n    }\n\n    if (maskkey != 0) {\n        int i;\n        int len32 = (size+3)/4;\n        uint32_t* buffer32 = (uint32_t*)buffer;\n        for (i = 0; i < len32; i++) {\n            buffer32[i] ^= maskkey;\n        }\n    }\n\n    return n;\n}\n\n/* Read a complete frame from the WebSocket client:\n * - Make sure that buffer size is a multiple of 4 (for unmasking).\n * Returns packet size on success.\n * On error (e.g. packet too large for buffer), closes the socket, and\n * returns -1.\n */\nstatic int socket_client_read_frame(char* buffer, int size) {\n    int buflen = 0;\n    int fin = 0;\n    uint32_t maskkey;\n    int retry = 0;\n\n    /* Read possibly fragmented message from WebSocket. */\n    while (fin != 1) {\n        int len = socket_client_read_frame_header(&fin, &maskkey, &retry);\n\n        if (retry)\n            continue;\n\n        if (len < 0)\n            return -1;\n\n        if (len+buflen > size) {\n            error(\"Response too long: (>%d bytes).\", size);\n            socket_client_close(1);\n            return -1;\n        }\n\n        if (socket_client_read_frame_data(buffer + buflen, len, maskkey) < 0) {\n            socket_client_close(0);\n            return -1;\n        }\n        buflen += len;\n    }\n\n    return buflen;\n}\n\n/* Send a version packet to the extension, and read VOK reply. */\nstatic int socket_client_sendversion(char* version) {\n    int versionlen = strlen(version);\n    char* outbuf = malloc(FRAMEMAXHEADERSIZE + versionlen);\n    memcpy(outbuf + FRAMEMAXHEADERSIZE, version, versionlen);\n\n    log(2, \"Sending version packet (%s).\", version);\n\n    if (socket_client_write_frame(outbuf, versionlen, WS_OPCODE_TEXT, 1) < 0) {\n        error(\"Write error.\");\n        socket_client_close(0);\n        free(outbuf);\n        return -1;\n    }\n    free(outbuf);\n\n    /* Read response back */\n    char buffer[256];\n    int buflen = socket_client_read_frame(buffer, sizeof(buffer));\n\n    buffer[buflen == 256 ? 255 : buflen] = 0;\n    if (buflen != 3 || strcmp(buffer, \"VOK\")) {\n        int i;\n        for (i = 0; i < buflen; i++) {\n            if (!isprint(buffer[i]))\n                buffer[i] = '?';\n        }\n        error(\"Invalid response: %s.\", buffer);\n        socket_client_close(1);\n        return -1;\n    }\n\n    log(2, \"Received VOK.\");\n    return 0;\n}\n\n/* Bitmask indicating if we received everything we need in the header */\nconst int OK_GET = 0x01;         /* GET {PATH} HTTP/1.1 */\nconst int OK_GET_PATH = 0x02;    /* {PATH} == / in GET request */\nconst int OK_UPGRADE = 0x04;     /* Upgrade: websocket */\nconst int OK_CONNECTION = 0x08;  /* Connection: Upgrade */\nconst int OK_SEC_VERSION = 0x10; /* Sec-WebSocket-Version: {VERSION} */\nconst int OK_VERSION = 0x20;     /* {VERSION} == 13 */\nconst int OK_SEC_KEY = 0x40;     /* Sec-WebSocket-Key: 24 bytes */\nconst int OK_HOST = 0x80;        /* Host: localhost:PORT */\nconst int OK_ALL = 0xFF;         /* Final correct value is 0xFF */\n\n/* Send an error on a new client socket, then close the socket. */\nstatic void socket_server_error(int newclient_fd, int ok) {\n    /* Values found only in WebSocket header */\n    const int OK_WEBSOCKET = OK_UPGRADE|OK_CONNECTION|OK_SEC_VERSION|\n                             OK_VERSION|OK_SEC_KEY;\n    /* Values found in WebSocket header of a possibly wrong version */\n    const int OK_OTHER_VERSION = OK_GET|OK_UPGRADE|OK_CONNECTION|OK_SEC_VERSION;\n\n    char buffer[BUFFERSIZE];\n\n    if ((ok & OK_GET) &&\n            (!(ok & OK_GET_PATH) || !(ok & OK_WEBSOCKET))) {\n        /* Path is not /, or / but clearly not a WebSocket handshake: 404 */\n        strncpy(buffer,\n                \"HTTP/1.1 404 Not Found\\r\\n\"\n                \"\\r\\n\"\n                \"<h1>404 Not Found</h1>\", BUFFERSIZE);\n    } else if ((ok & OK_OTHER_VERSION) == OK_OTHER_VERSION &&\n               !(ok & OK_VERSION)) {\n        /* We received something that looks like a WebSocket handshake,\n         * but wrong version */\n        strncpy(buffer,\n                \"HTTP/1.1 400 Bad Request\\r\\n\"\n                \"Sec-WebSocket-Version: 13\\r\\n\"\n                \"\\r\\n\", BUFFERSIZE);\n    } else {\n        /* Generic answer */\n        strncpy(buffer,\n                \"HTTP/1.1 400 Bad Request\\r\\n\"\n                \"\\r\\n\"\n                \"<h1>400 Bad Request</h1>\", BUFFERSIZE);\n    }\n\n    log(3, \"answer:\\n%s===\", buffer);\n\n    /* Ignore errors */\n    block_write(newclient_fd, buffer, strlen(buffer));\n\n    close(newclient_fd);\n}\n\n/* Read and parse HTTP header.\n * Returns 0 if the header is valid. websocket_key must be at least SECKEY_LEN\n * bytes long, and contains the value of Sec-WebSocket-Key on success.\n * Returns < 0 in case of error: in that case newclient_fd is closed.\n */\nstatic int socket_server_read_header(int newclient_fd, char* websocket_key) {\n    int first = 1;\n    char buffer[BUFFERSIZE];\n    int ok = 0x00;\n\n    char* pbuffer = buffer;\n    int n = read(newclient_fd, buffer, BUFFERSIZE);\n    if (n <= 0) {\n        syserror(\"Cannot read from client.\");\n        close(newclient_fd);\n        return -1;\n    }\n\n    while (1) {\n        /* Start of current line (until ':' for key-value pairs) */\n        char* key = pbuffer;\n        /* Start of value in current line (part after ': '). */\n        char* value = NULL;\n\n        /* Read a line of header, splitting key-value pairs if possible. */\n        while (1) {\n            if (n == 0) {\n                /* No more data in buffer: shift data so that key == buffer,\n                 * and try reading again. */\n                memmove(buffer, key, pbuffer-key);\n                if (value)\n                    value -= (key-buffer);\n                pbuffer -= (key-buffer);\n                key = buffer;\n\n                n = read(newclient_fd, pbuffer, BUFFERSIZE-(pbuffer-buffer));\n                if (n <= 0) {\n                    syserror(\"Cannot read from client.\");\n                    close(newclient_fd);\n                    return -1;\n                }\n            }\n\n            /* Detect new line:\n             * HTTP RFC says it must be CRLF, but we accept LF. */\n            if (*pbuffer == '\\n') {\n                if (*(pbuffer-1) == '\\r')\n                    *(pbuffer-1) = '\\0';\n                else\n                    *pbuffer = '\\0';\n                n--; pbuffer++;\n                break;\n            }\n\n            /* Detect \"Key: Value\" pairs, on all lines but the first one. */\n            if (!first && !value && *pbuffer == ':') {\n                value = pbuffer+2;\n                *pbuffer = '\\0';\n            }\n\n            n--; pbuffer++;\n        }\n\n        log(3, \"HTTP header: key=%s; value=%s.\", key, value);\n\n        /* Empty line indicates end of header. */\n        if (strlen(key) == 0 && !value)\n            break;\n\n        if (first) {  /* Normally GET / HTTP/1.1 */\n            first = 0;\n\n            char* tok = strtok(key, \" \");\n            if (!tok || strcmp(tok, \"GET\")) {\n                error(\"Invalid HTTP method (%s).\", tok);\n                continue;\n            }\n\n            tok = strtok(NULL, \" \");\n            if (!tok || strcmp(tok, \"/\")) {\n                error(\"Invalid path (%s).\", tok);\n            } else {\n                ok |= OK_GET_PATH;\n            }\n\n            tok = strtok(NULL, \" \");\n            if (!tok || strcmp(tok, \"HTTP/1.1\")) {\n                error(\"Invalid HTTP version (%s).\", tok);\n                continue;\n            }\n\n            ok |= OK_GET;\n        } else {\n            if (!value) {\n                error(\"Invalid HTTP header (%s).\", key);\n                socket_server_error(newclient_fd, 0x00);\n                return -1;\n            }\n\n            if (!strcmp(key, \"Upgrade\") && !strcmp(value, \"websocket\")) {\n                ok |= OK_UPGRADE;\n            } else if (!strcmp(key, \"Connection\") &&\n                       !strcmp(value, \"Upgrade\")) {\n                ok |= OK_CONNECTION;\n            } else if (!strcmp(key, \"Sec-WebSocket-Version\")) {\n                ok |= OK_SEC_VERSION;\n                if (strcmp(value, \"13\")) {\n                    error(\"Invalid Sec-WebSocket-Version: '%s'.\", value);\n                    continue;\n                }\n                ok |= OK_VERSION;\n            } else if (!strcmp(key, \"Sec-WebSocket-Key\")) {\n                if (strlen(value) != SECKEY_LEN) {\n                    error(\"Invalid Sec-WebSocket-Key: '%s'.\", value);\n                    continue;\n                }\n                memcpy(websocket_key, value, SECKEY_LEN);\n                ok |= OK_SEC_KEY;\n            } else if (!strcmp(key, \"Host\")) {\n                char strbuf[32];\n                snprintf(strbuf, 32, \"localhost:%d\", port);\n\n                if (strcmp(value, strbuf)) {\n                    error(\"Invalid Host field: '%s'.\", value);\n                    continue;\n                }\n                ok |= OK_HOST;\n            }\n        }\n    }\n\n    if (ok != OK_ALL) {\n        error(\"Some WebSocket headers missing (%x).\", ~ok & OK_ALL);\n        socket_server_error(newclient_fd, ok);\n        return -1;\n    }\n\n    return 0;\n}\n\n/* Accept a new client connection on the server socket. */\nstatic int socket_server_accept(char* version) {\n    int newclient_fd;\n    struct sockaddr_in client_addr;\n    unsigned int client_addr_len = sizeof(client_addr);\n    char buffer[BUFFERSIZE];\n\n    newclient_fd = accept(server_fd,\n                          (struct sockaddr*)&client_addr, &client_addr_len);\n\n    if (newclient_fd < 0) {\n        syserror(\"Error accepting new connection.\");\n        return -1;\n    }\n\n    /* key from client + GUID */\n    int websocket_keylen = SECKEY_LEN + strlen(GUID);\n    char websocket_key[websocket_keylen];\n\n    /* Read and parse HTTP header */\n    if (socket_server_read_header(newclient_fd, websocket_key) < 0) {\n        return -1;\n    }\n\n    log(1, \"Header read successfully.\");\n\n    /* Compute sha1+base64 response (RFC section 4.2.2, paragraph 5.4) */\n\n    char sha1[SHA1_LEN];\n\n    /* Some margin so we can read the full output of base64 */\n    int b64_len = SHA1_BASE64_LEN + 4;\n    char b64[b64_len];\n    int i;\n\n    memcpy(websocket_key + SECKEY_LEN, GUID, strlen(GUID));\n\n    /* SHA-1 is 20 bytes long (40 characters in hex form) */\n    if (popen2(\"sha1sum\", NULL, websocket_key, websocket_keylen,\n               buffer, BUFFERSIZE) < 2*SHA1_LEN) {\n        error(\"sha1sum response too short.\");\n        exit(1);\n    }\n\n    /* Make sure sscanf does not read too much data */\n    buffer[2*SHA1_LEN + 1] = '\\0';\n    for (i = 0; i < SHA1_LEN; i++) {\n        unsigned int value;\n        if (sscanf(&buffer[i*2], \"%02x\", &value) != 1) {\n            buffer[2*SHA1_LEN] = 0;\n            error(\"Cannot read SHA-1 sum (%s).\", buffer);\n            exit(1);\n        }\n        sha1[i] = (char)value;\n    }\n\n    /* base64 encoding of SHA1_LEN bytes must be SHA1_BASE64_LEN bytes long.\n     * Either the output is exactly SHA1_BASE64_LEN long, or the last character\n     * is a line feed (RFC 3548 forbids other characters in output) */\n    int n = popen2(\"base64\", NULL, sha1, SHA1_LEN, b64, b64_len);\n    if (n < SHA1_BASE64_LEN ||\n            (n != SHA1_BASE64_LEN && b64[SHA1_BASE64_LEN] != '\\r' &&\n             b64[SHA1_BASE64_LEN] != '\\n')) {\n        error(\"Invalid base64 response.\");\n        exit(1);\n    }\n    b64[SHA1_BASE64_LEN] = '\\0';\n\n    int len = snprintf(buffer, BUFFERSIZE,\n                       \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n                       \"Upgrade: websocket\\r\\n\"\n                       \"Connection: Upgrade\\r\\n\"\n                       \"Sec-WebSocket-Accept: %s\\r\\n\"\n                       \"\\r\\n\", b64);\n\n    if (len == BUFFERSIZE) {\n        error(\"Response length > %d.\", BUFFERSIZE);\n        exit(1);\n    }\n\n    log(3, \"HTTP response:\\n%s===\", buffer);\n\n    if (block_write(newclient_fd, buffer, len) != len) {\n        syserror(\"Cannot write response.\");\n        close(newclient_fd);\n        return -1;\n    }\n\n    log(2, \"Response sent.\");\n\n    /* Close existing connection, if any. */\n    if (client_fd >= 0)\n        socket_client_close(1);\n\n    client_fd = newclient_fd;\n\n    return socket_client_sendversion(version);\n}\n\n/* Initialise WebSocket server */\nstatic void socket_server_init(int port_) {\n    struct sockaddr_in server_addr;\n    int optval;\n\n    port = port_;\n\n    server_fd = socket(AF_INET, SOCK_STREAM, 0);\n    if (server_fd < 0) {\n        syserror(\"Cannot create server socket.\");\n        exit(1);\n    }\n\n    /* SO_REUSEADDR to make sure the server can restart after a crash. */\n    optval = 1;\n    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));\n\n    /* Listen on loopback interface, port PORT. */\n    memset(&server_addr, 0, sizeof(server_addr));\n    server_addr.sin_family = AF_INET;\n    server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n    server_addr.sin_port = htons(port);\n\n    if (bind(server_fd,\n             (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {\n        syserror(\"Cannot bind server socket.\");\n        exit(1);\n    }\n\n    if (listen(server_fd, 5) < 0) {\n        syserror(\"Cannot listen on server socket.\");\n        exit(1);\n    }\n}\n\n#endif /* WEBSOCKET_H_ */\n"
  },
  {
    "path": "src/xi2event.c",
    "content": "/* Copyright (c) 2016 The crouton Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Monitors and displays XInput 2 raw events, such as key presses, mouse\n * motion/clicks, etc.\n */\n\n#include <X11/Xlib.h>\n#include <X11/extensions/XInput.h>\n#include <X11/extensions/XInput2.h>\n#include <X11/Xutil.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/* Print a XIRawEvent, including the list of valuators, all on one line. */\nstatic void print_rawevent(XIRawEvent *event) {\n    int i;\n    int lasti = -1;\n    double *val;\n\n    printf(\"EVENT type %d \", event->evtype);\n    printf(\"device %d %d \", event->deviceid, event->sourceid);\n    printf(\"detail %d \", event->detail);\n    printf(\"valuators\");\n\n    /* Get the index of the last valuator that is set */\n    for (i = 0; i < event->valuators.mask_len * 8; i++) {\n        if (XIMaskIsSet(event->valuators.mask, i)) {\n            lasti = i;\n        }\n    }\n\n    /* Print each valuator's value, nan if the valuator is not set. */\n    val = event->valuators.values;\n    for (i = 0; i <= lasti; i++) {\n        if (XIMaskIsSet(event->valuators.mask, i)) {\n            printf(\" %.2f\", *val++);\n        } else {\n            printf(\" nan\");\n        }\n    }\n    printf(\"\\n\");\n}\n\nvoid usage(char* argv0) {\n    fprintf(stderr, \"%s [-1]\\n\", argv0);\n    fprintf(stderr, \"   Monitors and displays XInput 2 raw events.\\n\");\n    fprintf(stderr, \"   -1: only wait for one event, then exit.\\n\");\n    exit(1);\n}\n\nint main(int argc, char *argv[]) {\n    int firstev, firsterr;\n    int xi_opcode = -1;\n    int one_event = 0;\n    int terminate = 0;\n\n    /* stdout: line buffering */\n    setvbuf(stdout, NULL, _IOLBF, 0);\n\n    /* Parse arguments */\n    if (argc == 2) {\n        printf(\"%s\", argv[1]);\n        if (strcmp(argv[1], \"-1\") == 0)\n            one_event = 1;\n        else\n            usage(argv[0]);\n    } else if (argc > 2) {\n        usage(argv[0]);\n    }\n\n    Display* display = XOpenDisplay(NULL);\n\n    if (display == NULL) {\n        fprintf(stderr, \"Unable to connect to X server\\n\");\n        exit(1);\n    }\n\n    if (!XQueryExtension(display, \"XInputExtension\",\n                         &xi_opcode, &firstev, &firsterr)) {\n        fprintf(stderr, \"X Input extension not available.\\n\");\n        exit(1);\n    }\n\n    /* Listen on root window so that we do not need to create our own. */\n    Window win = DefaultRootWindow(display);\n\n    XIEventMask eventmask;\n\n    eventmask.deviceid = XIAllMasterDevices;\n    unsigned char mask[XIMaskLen(XI_LASTEVENT)];\n    memset(mask, 0, sizeof(mask));\n    XISetMask(mask, XI_RawKeyPress);\n    XISetMask(mask, XI_RawKeyRelease);\n    XISetMask(mask, XI_RawButtonPress);\n    XISetMask(mask, XI_RawButtonRelease);\n    XISetMask(mask, XI_RawMotion);\n    XISetMask(mask, XI_RawTouchBegin);\n    XISetMask(mask, XI_RawTouchUpdate);\n    XISetMask(mask, XI_RawTouchEnd);\n    eventmask.mask = mask;\n    eventmask.mask_len = sizeof(mask);\n\n    /* select on the window */\n    XISelectEvents(display, win, &eventmask, 1);\n\n    XEvent event;\n    XGenericEventCookie *cookie = &event.xcookie;\n\n    while (!terminate) {\n        XNextEvent(display, &event);\n\n        if (XGetEventData(display, cookie)) {\n            if (cookie->extension == xi_opcode && cookie->type == GenericEvent) {\n                switch(cookie->evtype) {\n                case XI_RawKeyPress:\n                case XI_RawKeyRelease:\n                case XI_RawButtonPress:\n                case XI_RawButtonRelease:\n                case XI_RawMotion:\n                case XI_RawTouchBegin:\n                case XI_RawTouchUpdate:\n                case XI_RawTouchEnd:\n                    print_rawevent(cookie->data);\n                    if (one_event)\n                        terminate = 1;\n                    break;\n                default:\n                    break;\n                }\n            }\n            XFreeEventData(display, cookie);\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "targets/audio",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='core'\nDESCRIPTION=\"Support audio playback via Chromium OS's audio system.\"\nCHROOTBIN='volume'\nCHROOTETC='pulseaudio-default.pa'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n\n# Fetch CRAS source\n\nCRASBUILDTMP=\"`mktemp -d crouton-cras.XXXXXX --tmpdir=/tmp`\"\n\naddtrap \"rm -rf --one-file-system '$CRASBUILDTMP'\"\n\n# ADHD_HEAD is normally autodetected, but it can be set manually using a custom\n# target sourced before this one (used for automated testing)\nif [ -z \"$ADHD_HEAD\" ]; then\n    # Chrome OS version (e.g. 4100.86.0)\n    CROS_VER=\"`sed -n 's/^CHROMEOS_RELEASE_VERSION=//p' /var/host/lsb-release`\"\n\n    cras_version=\"`cat /var/host/cras-version 2>/dev/null || true`\"\n    ADHD_HEAD=\"${cras_version##*-}\"\n    # Make sure ADHD_HEAD looks like a commit id\n    if [ \"${#ADHD_HEAD}\" -ne 40 -o \"${head%[^0-9a-f]*}\" != \"$head\" ]; then\n        echo \"Empty or invalid cras-version (${cras_version}).\" 1>&2\n        ADHD_HEAD=\"\"\n    fi\nfi\n\n\n# We're currently unable to build cras on version 111 and above, so skip it.\nif [ -n \"${CROS_VER:-}\" ] && [ \"${CROS_VER%%.*}\" -gt 15278 ]; then\n    TIPS=\"$TIPS\nAudio build was skipped due to unsupported toolchain changes in cras.\nYou can add your user to hwaudio to directly use the hardware audio, but it may\nbe unreliable as it will conflict with cras on Chromium OS.\n\"\nelse\n\n\necho \"Fetching CRAS (branch $ADHD_HEAD)...\" 1>&2\n\n# Try to download the CRAS commit id first, and fall back on master if that is\n# not found (see crbug.com/417820).\n\narchive=\"$CRASBUILDTMP/adhd.tar.gz\"\nlog=\"$CRASBUILDTMP/wget.log\"\nurlbase=\"https://chromium.googlesource.com/chromiumos/third_party/adhd/+archive\"\n( wget -O \"$archive\" \"$urlbase/$ADHD_HEAD.tar.gz\" 2>&1 \\\n                                    || echo \"Error fetching CRAS\" ) | tee \"$log\"\nif tail -n 1 \"$log\" | grep -q \"^Error\"; then\n    if grep -q \"404 Not Found\" \"$log\" 2>/dev/null; then\n        echo \"Branch not found, falling back on master (audio might not work)...\" 1>&2\n        ADHD_HEAD=\"master\"\n        wget -O \"$archive\" \"$urlbase/$ADHD_HEAD.tar.gz\"\n    else\n        # wget already printed an explicit error\n        exit 1\n    fi\nfi\n\n# Build CRAS ALSA plugin for the given architecture ($1)\n# A blank parameter means we are building for the native architecture.\nbuild_cras() {\n    local cras_arch=\"$1\"\n    local pkgsuffix=''\n    local pkgdepextra=''\n    local archextrapath=''\n    local pkgconfigpath=''\n    local archgccflags=''\n    if [ -n \"$cras_arch\" ]; then\n        pkgsuffix=\":$cras_arch\"\n        pkgdepextra='gcc-multilib'\n        archextrapath=\"/$cras_arch-linux-gnu\"\n        pkgconfigpath=\"/usr/lib$archextrapath/pkgconfig\"\n        archgccflags='-m32'\n\n        # Add foreign architecture, if necessary\n        if ! dpkg --print-foreign-architectures | grep -q \"^$cras_arch$\"; then\n            echo \"Adding foreign architecture $cras_arch to dpkg...\" 1>&2\n            dpkg --add-architecture \"$cras_arch\"\n            apt-get update || true\n        fi\n    fi\n\n    # Install CRAS dependencies\n    install --minimal alsa-utils \\\n        libasound2$pkgsuffix \\\n        libasound2-plugins$pkgsuffix \\\n        libspeexdsp1$pkgsuffix\n\n    install --minimal --asdeps gcc $pkgdepextra libc6-dev$pkgsuffix \\\n        pkg-config libspeexdsp-dev$pkgsuffix\n\n    # precise does not allow libasound2-dev and libasound2-dev:i386 to be\n    # installed simultaneously\n    if release -le precise && [ -n \"$cras_arch\" ]; then\n        install --minimal --asdeps libasound2-dev\n        # Manually link .so file\n        libasoundso=\"/usr/lib$archextrapath/libasound.so\"\n        if [ ! -f \"$libasoundso\" ]; then\n            addtrap \"rm -f '$libasoundso' 2>/dev/null\"\n            ln -sfT libasound.so.2 \"$libasoundso\"\n        fi\n        ALSALIBDIR=\"/usr/lib$archextrapath/alsa-lib\"\n    else\n        install --minimal --asdeps libasound2-dev$pkgsuffix\n        ALSALIBDIR=\"`PKG_CONFIG_PATH=\"$pkgconfigpath\" \\\n                                pkg-config --variable=libdir alsa`/alsa-lib\"\n    fi\n\n    # Start subshell for compilation\n    (\n        cd \"$CRASBUILDTMP\"\n\n        # Make sure we start fresh\n        rm -rf --one-file-system cras\n\n        # -m prevents \"time stamp is in the future\" spam\n        tar -xmf adhd.tar.gz cras/src\n\n        cd cras/src\n\n        # Create version file\n        echo '#define VCSID \"crouton-'\"$ADHD_HEAD\"'\"' > common/cras_version.h\n\n        install --minimal --asdeps patch\n\n        # Remove SBC dependency\n        sed -e 's/#include <sbc.*//' -i common/cras_sbc_codec.h\n        cat > common/cras_sbc_codec.c <<END\n#include <stdint.h>\n#include <stdlib.h>\n#include \"cras_audio_codec.h\"\n\nstruct cras_audio_codec *cras_sbc_codec_create(uint8_t freq,\n\t\t   uint8_t mode, uint8_t subbands, uint8_t alloc,\n\t\t   uint8_t blocks, uint8_t bitpool) {\n    abort();\n}\nvoid cras_sbc_codec_destroy(struct cras_audio_codec *codec) {\n    abort();\n}\nEND\n\n        # Find the cras test client\n        cras_test_client='tools/cras_test_client/cras_test_client.c'\n        # FIXME: remove when 12334.0.0 (R77) becomes minimum target\n        if [ ! -f \"$cras_test_client\" ]; then\n            cras_test_client='tests/cras_test_client.c'\n        fi\n\n        # Drop SBC constants\n        sed -e 's/SBC_[A-Z0-9_]*/0/g' -i \"$cras_test_client\"\n\n\t# FIXME(crbug.com/864815) Remove when fixed upstream\n\tif [ \"$cras_arch\" = \"i386\" ]; then\n\t    sed -i -e 's/Dumping AEC info to %s, stream %lu/Dumping AEC info to %s, stream %llu/' \\\n\t\t\"$cras_test_client\"\n\tfi\n\tsed -i -e 's/cras_stream_id_t stream_id;/cras_stream_id_t stream_id = 0;/' \\\n\t    \"$cras_test_client\"\n\n\t# FIXME(http://crrev.com/c/1759370): Remove when R78 becomes minimum target\n\tsed -i -e 's/\\(strncpy.*sizeof(info.name)\\));/\\1 - 1); info.name[sizeof(info.name) - 1] = '\"'\"'\\\\0'\"'\"';/' \\\n\t\tcommon/cras_shm.c\n\tsed -i -e 's/^struct \\([^_]\\)/struct __attribute__((__packed__)) \\1/' \\\n\t\tcommon/cras_messages.h\n\n        # Directory to install CRAS library/binaries\n        CRASLIBDIR=\"/usr/local$archextrapath/lib\"\n        CRASBINDIR=\"/usr/local$archextrapath/bin\"\n\n        echo \"Compiling CRAS (${cras_arch:-native})...\" 1>&2\n        # Convert Makefile.am to a shell script, and run it.\n        {\n            convert_automake\n\n            echo '\n                CFLAGS=\"$CFLAGS -DCRAS_SOCKET_FILE_DIR=\\\"/var/run/cras\\\" -Wno-error\"\n\n                buildlib libcras\n\n                # Pass -rpath=$CRASLIBDIR to linker, so we do not need to add\n                # the directory to ldconfig search path (some distributions do\n                # not include /usr/local/lib in /etc/ld.so.conf).\n                # We also need to add \"-L.\" as we are not using .la files.\n                extraflags=\"-Wl,-rpath='\"$CRASLIBDIR\"' -L.\"\n\n                buildlib libasound_module_pcm_cras \"$extraflags\"\n                buildlib libasound_module_ctl_cras \"$extraflags\"\n                buildexe cras_test_client \"$extraflags\"\n            '\n        } | sh -s -e $SETOPTIONS\n\n        echo \"Installing CRAS...\" 1>&2\n\n        mkdir -p \"$CRASBINDIR/\" \"$CRASLIBDIR/\" \"$ALSALIBDIR/\"\n        # Only install libcras.so.X.Y.Z\n        /usr/bin/install -s libcras.so.*.* \"$CRASLIBDIR/\"\n        # Generate symbolic link to libcras.so.X\n        ldconfig -l \"$CRASLIBDIR\"/libcras.so.*.*\n        /usr/bin/install -s libasound_module_pcm_cras.so \"$ALSALIBDIR/\"\n        /usr/bin/install -s libasound_module_ctl_cras.so \"$ALSALIBDIR/\"\n        /usr/bin/install -s cras_test_client \"$CRASBINDIR/\"\n    ) # End compilation subshell\n}\n\n# On x86_64, the ALSA plugin needs to be compiled for both 32-bit and 64-bit\n# to allow audio playback using 32-bit applications.\nif [ \"$ARCH\" = 'amd64' ]; then\n    build_cras 'i386'\nfi\n\n# Build CRAS for native architecture\nbuild_cras\n\nalsaconf='/etc/alsa/conf.d'\nif [ ! -d \"$alsaconf\" ]; then\n    alsaconf='/usr/share/alsa/alsa.conf.d'\n    if [ ! -d \"$alsaconf\" ]; then\n        echo \"Unable to find correct ALSA configuration directory.\" 1>&2\n        alsaconf='/tmp'\n    fi\nfi\necho \"Writing ALSA config into $alsaconf/10-cras.conf\" 1>&2\ncat > \"$alsaconf/10-cras.conf\" <<EOF\npcm.cras {\n    type cras\n    hint {\n        show on\n        description \"Chromium OS Audio Server\"\n    }\n}\nctl.cras {\n    type cras\n}\n\n# Default: route all audio through the CRAS plugin.\npcm.!default {\n    type cras\n    hint {\n        show on\n        description \"Default ALSA Output (currently Chromium OS Audio Server)\"\n    }\n}\nctl.!default {\n    type cras\n}\nEOF\n\n# Configure pulseaudio even if it is not installed (yet).\n# We use ~/.config/pulse/default.pa for this purpose, but the main user may\n# not have been created yet, so we add a script in /etc/profile.d to link\n# ~/.config/pulse/default.pa to /etc/crouton/pulseaudio-default.pa.\n\npaconfigdir='$HOME/.config/pulse'\n# Old versions of pulseaudio use ~/.pulse/default.pa\nif release -le quantal -le wheezy; then\n    paconfigdir='$HOME/.pulse'\nfi\n\nprofiledsh='/etc/profile.d/crouton-pulseaudio-cras.sh'\n# Make sure symbolic link is setup on login\necho '#!/bin/sh\n\ndefaultpa=\"'\"$paconfigdir\"'/default.pa\"\n# Do not install if user is root, or $HOME does not exist\nif [ \"`id -u`\" -ne 0 -a -d \"$HOME\" -a ! -e \"$defaultpa\" ]; then\n    mkdir -p \"'\"$paconfigdir\"'\"\n    ln -sfT /etc/crouton/pulseaudio-default.pa \"$defaultpa\"\nfi' > \"$profiledsh\"\n\nchmod 755 \"$profiledsh\"\n\nTIPS=\"$TIPS\nAudio from the chroot will now be forwarded to CRAS (Chromium OS audio server),\nthrough an ALSA plugin.\n\nFuture Chromium OS upgrades may break compatibility with the installed version\nof CRAS. Should this happen, simply update your chroot.\n\"\n\n\nfi  # disable building audio on newer platforms due to toolchain changes\n"
  },
  {
    "path": "targets/chrome",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nDESCRIPTION='Google Chrome browser, stable channel.'\n. \"${TARGETSDIR:=\"$PWD\"}/chrome-common\"\n\n### Append to prepare.sh:\nCHANNEL='stable'\n### append chrome-common\n"
  },
  {
    "path": "targets/chrome-beta",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nDESCRIPTION='Google Chrome browser, beta channel.'\n. \"${TARGETSDIR:=\"$PWD\"}/chrome-common\"\n\n### Append to prepare.sh:\nCHANNEL='beta'\n### append chrome-common\n"
  },
  {
    "path": "targets/chrome-common",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This isn't a real target; it's the common commands for installing the various\n# channels of the Google Chrome browser that the relevant targets source.\n\n# This command expects CHANNEL to be set with the requested Google Chrome\n# channel: stable, beta, or unstable.\n\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' -a \"${ARCH#arm}\" != \"$ARCH\" ]; then\n    echo 'Google Chrome does not yet have an ARM build. Installing Chromium instead.' 1>&2\n    TARGET=chromium\n    . \"${TARGETSDIR:=\"$PWD\"}/$TARGET\"\nfi\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ] && release -lt saucy -lt jessie; then\n    echo \"Google Chrome is not supported on $RELEASE anymore. Installing Chromium instead.\" 1>&2\n    TARGET=chromium\n    . \"${TARGETSDIR:=\"$PWD\"}/$TARGET\"\nfi\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' -a \"$TARGET\" != 'chrome' ]; then\n    # Avoid installing multiple chrome targets\n    if grep -q \"^chrome\\$\" \"${TARGETDEDUPFILE:-/dev/null}\"; then\n        exit\n    else\n        echo \"chrome\" >> \"${TARGETDEDUPFILE:-/dev/null}\"\n    fi\nfi\nREQUIRES='x11'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n# Only download if it's not already installed, since it auto-updates\npkg=\"google-chrome-$CHANNEL\"\nif [ -n \"`list_uninstalled '' \"$pkg\"`\" ]; then\n    # Chrome needs hicolor-icon-theme to configure/install\n    install hicolor-icon-theme\n\n    url=\"https://dl.google.com/linux/direct/${pkg}_current_$ARCH.$PKGEXT\"\n    tmp=\"`mktemp crouton.XXXXXX --tmpdir=/tmp`\"\n    addtrap \"rm -f '$tmp'\"\n\n    wget \"$url\" -O \"$tmp\"\n\n    # Remove other providers of google-chrome, then install the package\n    for c in stable beta dev; do\n        c=\"google-chrome-$c\"\n        if [ -z \"`list_uninstalled '' \"$c\"`\" ]; then\n            remove \"$c\"\n        fi\n    done\n    install_pkg \"$tmp\"\nelse\n    echo \"$pkg is already installed.\"\nfi\n"
  },
  {
    "path": "targets/chrome-dev",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nDESCRIPTION='Google Chrome browser, dev channel.'\n. \"${TARGETSDIR:=\"$PWD\"}/chrome-common\"\n\n### Append to prepare.sh:\nCHANNEL='unstable'\n### append chrome-common\n"
  },
  {
    "path": "targets/chromium",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ] &&\n        [ \"$DISTRO\" = 'debian' -o \"$DISTRO\" = 'kali' ] &&\n        [ \"${ARCH#arm}\" != \"$ARCH\" ]; then\n    error 99 \"chromium target is not supported on Debian/ARM.\"\nfi\nREQUIRES='x11'\nDESCRIPTION=\"Chromium browser. Uses the distro's version, which may be old.\"\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall ubuntu=chromium-browser,chromium\n"
  },
  {
    "path": "targets/cli-extra",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='core'\nDESCRIPTION='Basic CLI tools such as ssh.'\nHOSTBIN='startcli'\nCHROOTBIN='croutonpowerd'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall dbus openssh-client kbd\n\nTIPS=\"$TIPS\nYou can start a shell in a new VT via the startcli host command: sudo startcli\n\"\n"
  },
  {
    "path": "targets/common",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This isn't a real target; it's the boilerplate that the other targets source.\n# The targets are simply sourced inside of install.sh, and so can expect to have\n# access to all all-caps variables (by convention; not a technical restriction).\n\n# Additionally, this common script expects the sourcing script to set\n# DESCRIPTION with the description of the target, and REQUIRES with a\n# space-separated list of other targets it needs.\n\n# The script can also specify a space-separated list of targets in PROVIDES;\n# this will prevent other targets from installing when this one is done first.\n\n# Finally, targets can set HOSTBIN, CHROOTBIN, and CHROOTETC with a\n# space-separated list of files to copy from host-bin, chroot-bin, and\n# chroot-etc into the appropriate places.\n\n# All targets must be idempotent since the user may Ctrl-C the process and run\n# it again.\n\nif [ -z \"$TARGET\" ]; then\n    TARGET=\"$0\"\n    SIMULATE=echo\nfi\n\nif [ \"$TARGETS\" = 'help' ]; then\n    if [ -n \"$DESCRIPTION\" ]; then\n        echo \"$TARGET\"\n        echo \"\t$DESCRIPTION\"\n        if [ -n \"$REQUIRES\" ]; then\n            echo \"\tRequires: $REQUIRES\"\n        fi\n    fi\nelif [ \"$TARGETNOINSTALL\" = \"p\" ]; then\n    # This will check the provides from /etc/crouton/targets and targets\n    # specified with -t. If there are duplicates we will respect the\n    # order of the targets specified. This means on an update targets\n    # specified with -t will have higher priority then already installed targets.\n    for t in $PROVIDES; do\n        echo \"$t=$TARGET\" >> \"${PROVIDESFILE:-/dev/null}\"\n    done\nelif [ -z \"$TARGETNOINSTALL\" -o -n \"$RESTOREHOSTBIN\" ]; then\n    # Avoid double-adding targets\n    if grep -q \"^$TARGET\\$\" \"${TARGETDEDUPFILE:-/dev/null}\"; then\n        exit\n    else\n        echo \"$TARGET\" >> \"${TARGETDEDUPFILE:-/dev/null}\"\n    fi\n    # Avoid adding things already provided\n    for t in $PROVIDES; do\n        echo \"$t\" >> \"${TARGETDEDUPFILE:-/dev/null}\"\n    done\n    # Source the prerequisites\n    for t in $REQUIRES; do\n        # If it is already provided change it to the target what provides it.\n        provider=\"`awk -F= '/'\"$t\"'=/{print $2; exit}' \"${PROVIDESFILE:-/dev/null}\"`\"\n        t=\"${provider:-\"$t\"}\"\n        (TARGET=\"$t\" PROVIDES='' HOSTBIN='' CHROOTBIN='' CHROOTETC=''\n         . \"$TARGETSDIR/$t\")\n    done\n    # Add a print to the output\n    echo ''\n    echo \"echo 'Installing target $TARGET...' 1>&2\"\n    # Copy in requested items from host-bin and chroot-bin\n    for f in $HOSTBIN; do\n        echo \"Installing $f into the host...\" 1>&2\n        src=\"${HOSTBINDIR:-../host-bin}/$f\"\n        $SIMULATE installscript \"$src\" \"$BIN/\"\n        [ ! -h \"$src\" ] && $SIMULATE chmod 755 \"$BIN/$f\"\n    done\n\n    # Stop here if we are just restoring host-bin\n    if [ -n \"$RESTOREHOSTBIN\" ]; then\n        exit\n    fi\n\n    for f in $CHROOTBIN; do\n        echo \"Installing $f into the chroot...\" 1>&2\n        src=\"${CHROOTBINDIR:-../chroot-bin}/$f\"\n        $SIMULATE installscript \"$src\" \"$CHROOT/usr/local/bin/\"\n        [ ! -h \"$src\" ] && $SIMULATE chmod 755 \"$CHROOT/usr/local/bin/$f\"\n    done\n    for f in $CHROOTETC; do\n        echo \"Installing $f into the chroot...\" 1>&2\n        src=\"${CHROOTETCDIR:-../chroot-etc}/$f\"\n        $SIMULATE cp -fP \"$src\" \"$CHROOT/etc/crouton/\"\n        [ ! -h \"$src\" ] && $SIMULATE chmod 644 \"$CHROOT/etc/crouton/$f\"\n    done\n\n    # Copy all the source files to chroot\n    $SIMULATE rm -rf \"$CHROOT/usr/src/crouton\"\n    $SIMULATE cp -afP \"${SRCDIR:-../src}\" \"$CHROOT/usr/src/crouton\"\n\n    # Print out the target file below the ### line.\n    # This avoids having to escape everything like with cat<<EOF\n    # All ### lines are ignored.\n    # Lines with \"### append filename\" will queue a file to be processed after\n    # the current file is done.\n    # Lines that start with \"compile\" will have their source code inserted as a\n    # HERE document. In that case, we also look for local include file, and\n    # insert them as needed.\n    t=\"$TARGET\"\n    if [ \"${t#/}\" = \"$t\" ]; then\n        t=\"$TARGETSDIR/$t\"\n    fi\n    awk '\n        (FNR == 1) {\n            ok = 0;\n        }\n        /^###/ {\n            ok = 1;\n            if ($2 == \"append\") {\n                ARGV[ARGC++] = \"'\"$TARGETSDIR\"'/\" $3;\n            }\n            next;\n        }\n        ok && /^ *compile / {\n            src = $2 \".c\";\n        }\n        src && $NF != substr(\"\\\\\\\\\", 1, 1) {\n            print $0 \" < /usr/src/crouton/\" src\n            src = \"\";\n            next\n        }\n        ok;\n    ' \"$t\"\nfi\n# Break out of the subshell.\nexit\n"
  },
  {
    "path": "targets/core",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES=''\nDESCRIPTION='Performs core system configuration. Most users would want this.'\nCHROOTBIN='brightness croutonpowerd croutonversion host-dbus host-wayland'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\necho 'Preparing environment...' 1>&2\nif [ \"$VERSION\" != '#VERSION#' ]; then\n    sed -e \"s ^VERSION=.*\\$ VERSION='$VERSION' ;\" \\\n        -e \"s ^RELEASE=.*\\$ RELEASE='$RELEASE' ;\" \\\n        -e \"s ^ARCH=.*\\$ ARCH='$ARCH' ;\" \\\n        -i '/usr/local/bin/croutonversion'\nfi\n\n# Create the new environment file\noldenv='/etc/environment'\nnewenv='/etc/environment.new'\n{\n    echo '### begin crouton-generated environment variables'\n    if [ \"$PROXY\" = 'unspecified' -o \"$PROXY\" = '#PROXY#' ]; then\n        # Copy over previously generated content\n        awk '/^### end/ { exit }\n             x && tolower($0) ~ /^[a-z]*_proxy=/\n             /^### begin/ { x=1 }' \"$oldenv\" 2>/dev/null || true\n    elif [ -n \"$PROXY\" ]; then\n        for var in http_proxy HTTP_PROXY https_proxy HTTPS_PROXY \\\n                   ftp_proxy FTP_PROXY; do\n            echo \"$var='$PROXY'\"\n        done\n        for var in no_proxy NO_PROXY; do\n            echo \"$var='localhost,127.0.0.1'\"\n        done\n    fi\n    echo '### end crouton-generated environment variables'\n} > \"$newenv\"\n\n# Set proxy variables for this script\n. \"$newenv\"\nexport http_proxy https_proxy ftp_proxy\n\n# Copy in previous user-environment settings\nif [ -r \"$oldenv\" ]; then\n    awk '/^### begin/{x=1}!x;/^### end/{x=0}' \"$oldenv\" >> \"$newenv\"\nfi\nmv -f \"$newenv\" \"$oldenv\"\n\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    echo 'Preparing software sources...' 1>&2\n    if [ \"$PROXY\" != 'unspecified' -a \"$PROXY\" != '#PROXY#' ]; then\n        aptproxy='/etc/apt/apt.conf.d/80croutonproxy'\n        if [ -z \"$PROXY\" ]; then\n            rm -f \"$aptproxy\"\n        else\n            cat > \"$aptproxy\" <<EOF\nAcquire::http::proxy \"$PROXY\";\nAcquire::ftp::proxy \"$PROXY\";\nAcquire::https::proxy \"$PROXY\";\nEOF\n        fi\n    fi\n\n    # Only update sources.list if MIRROR is specified\n    if [ -n \"$MIRROR\" -a \"$MIRROR\" != 'unspecified' ]; then\n        if [ \"$DISTRO\" = 'ubuntu' ]; then\n            # Ubuntu has its own categories of packages\n            cat > /etc/apt/sources.list <<EOF\ndeb $MIRROR $RELEASE main restricted universe multiverse\ndeb-src $MIRROR $RELEASE main restricted universe multiverse\ndeb $MIRROR $RELEASE-updates main restricted universe multiverse\ndeb-src $MIRROR $RELEASE-updates main restricted universe multiverse\ndeb $MIRROR $RELEASE-security main restricted universe multiverse\ndeb-src $MIRROR $RELEASE-security main restricted universe multiverse\nEOF\n        else\n            # Not all Debian distros have all sets of repos\n            cat > /etc/apt/sources.list <<EOF\ndeb $MIRROR $RELEASE main non-free contrib\ndeb-src $MIRROR $RELEASE main non-free contrib\nEOF\n            # Only stable and testing have an updates channel\n            if release -lt sid; then\n                cat >> /etc/apt/sources.list <<EOF\ndeb $MIRROR $RELEASE-updates main non-free contrib\ndeb-src $MIRROR $RELEASE-updates main non-free contrib\nEOF\n            fi\n            # Some derivatives also have security updates\n            if release -le buster -eq kali; then\n                cat >> /etc/apt/sources.list <<EOF\ndeb $MIRROR2 $RELEASE/updates main non-free contrib\ndeb-src $MIRROR2 $RELEASE/updates main non-free contrib\nEOF\n            fi\n        fi\n    fi\n\n    # Update the package list.\n    # Ignore failures, as most are due to bad PPAs and are not critical.\n    apt-get -y update || true\n\n    echo 'Ensuring system is up-to-date...' 1>&2\n    apt-get -y dist-upgrade\nfi\n\n# On release upgrade, keyboard-configuration might be reconfigured.\nfixkeyboardmode\n\n# Install critical packages\ninstall --minimal sudo wget ca-certificates apt-transport-https\n\n# Generate and set default locale\nif ! grep -q '^[^#]*LANG=' /etc/default/locale 2>/dev/null && hash locale-gen 2>/dev/null; then\n    echo 'LANG=en_US.UTF-8' > '/etc/default/locale'\n    locale-gen --lang en_US.UTF-8\n    if [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n        dpkg-reconfigure locales\n    fi\nfi\n\n# Link debian_chroot to the chroot name\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    ln -sfT '/etc/crouton/name' '/etc/debian_chroot'\nfi\n\necho 'Syncing timezone...' 1>&2\n# Link the timezone to Chromium OS\n# Remove /etc/timezone: this tells Ubuntu/Debian that we are managing the\n# content of /etc/localtime manually, and that it should not erase the symbolic\n# link upon update, unless \"dpkg-reconfigure tzdata\" is called explicitly.\nrm -f /etc/timezone\n\n# /var/host/timezone/localtime is itself a symbolic link, but as long as the\n# zoneinfo packages in the chroot and Chromium OS are the same, it'll be fine\nln -sfT /var/host/timezone/localtime /etc/localtime\n\n# Link /etc/mtab to /proc/mounts. It's not totally accurate, but close enough,\n# as it at least has / and all the media-mounted devices.\nln -sfT /proc/mounts /etc/mtab\n"
  },
  {
    "path": "targets/e17",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ] && release -lt jessie -eq kali; then\n    error 99 \"e17 target is not supported on wheezy/kali.\"\nfi\nREQUIRES='gtk-extra'\nDESCRIPTION='Installs the enlightenment desktop environment. (Approx. 50MB)'\nHOSTBIN='starte17'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall e17\n\nTIPS=\"$TIPS\nYou can start e17 via the starte17 host command: sudo starte17\n\"\n"
  },
  {
    "path": "targets/extension",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='x11'\nDESCRIPTION='Clipboard synchronization and URL handling with Chromium OS.'\nCHROOTBIN='croutonclip croutonnotify croutonurlhandler'\nEXTENSION='gcpneefbbnfalgjniomfjknbcgkbijom'\n\n# Check if the extension is installed and add a mark to the preparation script\nif [ -z \"$TARGETNOINSTALL\" ]; then\n    if [ -d \"/home/chronos/user/Extensions/$EXTENSION\" ]; then\n        echo 'EXTENSION_INSTALLED=y'\n    else\n        echo 'EXTENSION_INSTALLED=n'\n    fi\nfi\n\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall x11-utils xsel\n\ncompile websocket ''\n\n# vtmonitor is needed for supporting xorg.\n# There are three ways xorg might be installed relative to extension: via a\n# dependency before extension, via explicit selection before extension, and via\n# explicit selection after extension.\n# The first is handled by checking XMETHOD, as the first X11 backend brought in\n# (explicit or otherwise) sets XMETHOD permanently.  The latter two are handled\n# by greping the explicit targets file for xorg.\nif [ \"$XMETHOD\" = 'xorg' ] || grep -q xorg /etc/crouton/targets; then\n    compile vtmonitor ''\nfi\n\n# Use croutonurlhandler as a fallback URL handler\nhandler='/usr/local/bin/croutonurlhandler'\nfor link in x-www-browser gnome-www-browser www-browser; do\n    if ! update-alternatives --query \"$link\" | grep -q \"$handler\"; then\n        update-alternatives --install \"/usr/bin/$link\" \"$link\" \"$handler\" 15\n    fi\ndone\n\nif [ \"$EXTENSION_INSTALLED\" = 'y' ]; then\n    TIPS=\"$TIPS\nYou already have the Chromium OS extension installed, so you're good to go!\n\"\nelse\n    TIPS=\"$TIPS\nYou must install the Chromium OS extension for integration with crouton to work.\nThe extension is available here:\n  https://chromewebstore.google.com/detail/crouton-integration/gcpneefbbnfalgjniomfjknbcgkbijom\n\"\nfi\n"
  },
  {
    "path": "targets/gnome",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='gtk-extra'\nDESCRIPTION='Installs the GNOME desktop environment. (Approx. 400MB)'\nHOSTBIN='startgnome'\nCHROOTBIN='crouton-noroot startgnome gnome-session-wrapper'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\nif release -lt jessie -lt sana -lt trusty; then\n  legacy_session_package=\"gnome-session-fallback\"\nelse\n  legacy_session_package=\"gnome-session-flashback\"\nfi\n\nif release -lt bionic -lt buster; then\n  gnome_themes=\"gnome-themes-standard\"\nelse\n  gnome_themes=\"gnome-themes-extra\"\nfi\n\n### Append to prepare.sh:\ninstall --minimal \\\n  $legacy_session_package \\\n  dconf-cli \\\n  evolution-data-server \\\n  gnome-control-center \\\n  gnome-screensaver \\\n  gnome-session \\\n  gnome-shell \\\n  $gnome_themes \\\n  gtk2-engines-pixbuf \\\n  gvfs-backends \\\n  nautilus \\\n  pulseaudio \\\n  unzip\n\nmkdir -p /etc/dconf/profile\nmkdir -p /etc/dconf/db/local.d\ncat << EOF > /etc/dconf/profile/user\nuser-db:user\nsystem-db:local\nEOF\ncat << EOF > /etc/dconf/db/local.d/01-always_show_logout\n[org/gnome/shell]\nalways-show-log-out=true\nEOF\ndconf update\n\nTIPS=\"$TIPS\nYou can start GNOME via the startgnome host command: sudo startgnome\n\"\n"
  },
  {
    "path": "targets/gnome-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ] && release -lt quantal; then\n    error 99 \"gnome-desktop target is not available in $RELEASE.\"\nfi\nREQUIRES='gnome'\nDESCRIPTION='Installs GNOME along with common applications. (Approx. 1100MB)'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall ubuntu=ubuntu-gnome-desktop,task-gnome-desktop \\\n        -- network-manager xorg ubuntu~xenial=xserver-xorg-legacy,\n"
  },
  {
    "path": "targets/gtk-extra",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='x11'\nDESCRIPTION='GTK-based tools including gdebi and a simple browser.'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall_dummy network-manager network-manager-gnome\ninstall gdebi\nif release -lt buster -lt kali-rolling -lt artful; then\n  install gksu\nfi\n\nfor BROWSER in netsurf-gtk dillo hv3 \"\"; do\n    test -n \"$BROWSER\"\n    if install \"$BROWSER\"; then\n        bin=\"/usr/bin/$BROWSER\"\n        for link in x-www-browser gnome-www-browser; do\n            if ! update-alternatives --query \"$link\" | grep -q \"$bin\"; then\n                update-alternatives --install \"/usr/bin/$link\" \"$link\" \"$bin\" 10\n            fi\n        done\n        break\n    fi\ndone\n"
  },
  {
    "path": "targets/kde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='x11'\nDESCRIPTION='Installs a minimal KDE desktop environment. (Approx. 600MB)'\nHOSTBIN='startkde'\nCHROOTBIN='crouton-noroot startkde'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall --minimal kde-baseapps kde-runtime plasma-desktop pulseaudio \\\n        -- network-manager\n\nif release -lt xenial -lt kali -lt stretch; then\n    install --minimal kde-workspace\nelse\n    install --minimal kwin-x11\nfi\n\nTIPS=\"$TIPS\nYou can start KDE via the startkde host command: sudo startkde\n\"\n"
  },
  {
    "path": "targets/kde-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='kde'\nDESCRIPTION='Installs KDE along with common applications. (Approx. 1000MB)'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall ubuntu=kubuntu-desktop,task-kde-desktop -- network-manager\n"
  },
  {
    "path": "targets/keyboard",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n#\n# This adds support for Chromebook keyboard special keys.\n#\n# For example, we map Search+arrows to Page Up/Down/Home/End. This is\n# done at the browser level in Chromium OS (i.e., not at the hardware\n# or keymap level).\n#\n# The mapping of Search+F1-F10 is reversed compared to Chromium OS:\n# Pressing the back key still produces F1, Search+F1 is required to\n# generate XF86Back.\n#\n# We do this by adding an overlay, and using Super_L (Search key) as\n# the overlay latch.\n#\n# An additional mapping is needed to make sure that Super_R is not\n# sent when the Search key is released (this is a little strange,\n# and I'm not sure why this happens).\n\nREQUIRES='x11'\nDESCRIPTION='Adds support for Chromebook keyboard special keys.'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    # Make sure we start from a fresh package\n    apt-mark unhold xkb-data || true 2>/dev/null\n    apt-get -y install --reinstall xkb-data\nfi\n\nXKBROOT='/usr/share/X11/xkb'\nXKBROOTTMP=\"`mktemp -d crouton-xkb.XXXXXX --tmpdir=/tmp`\"\naddtrap \"rm -rf --one-file-system '$XKBROOTTMP'\"\nmkdir -p \"$XKBROOTTMP/compat\" \"$XKBROOTTMP/symbols\" \"$XKBROOTTMP/rules\"\n\ncat > \"$XKBROOTTMP/compat/chromebook\" <<END\n// Overlay1_Enable is a latch key for overlay1\n\ndefault partial xkb_compatibility \"overlay\"  {\n    interpret Overlay1_Enable+AnyOfOrNone(all) {\n        action= SetControls(controls=Overlay1);\n    };\n};\nEND\n\ncat > \"$XKBROOTTMP/symbols/chromebook\" <<END\n// This mapping assumes that inet(evdev) will also be sourced\npartial\nxkb_symbols \"overlay\" {\n    key <LWIN> { [ Overlay1_Enable ], overlay1=<LWIN> };\n\n    key <AB09> { overlay1=<INS> };\n\n    key <LEFT> { overlay1=<HOME> };\n    key <RGHT> { overlay1=<END> };\n    key <UP>   { overlay1=<PGUP> };\n    key <DOWN> { overlay1=<PGDN> };\n\n    key <FK01> { overlay1=<I247> };\n    key <I247> { [ XF86Back ] };\n    key <FK02> { overlay1=<I248> };\n    key <I248> { [ XF86Forward ] };\n    key <FK03> { overlay1=<I249> };\n    key <I249> { [ XF86Reload ] };\n    key <FK04> { overlay1=<I235> }; // XF86Display\n    key <FK05> { overlay1=<I250> };\n    key <I250> { [ XF86ApplicationRight ] };\n    key <FK06> { overlay1=<I232> }; // XF86MonBrightnessDown\n    key <FK07> { overlay1=<I233> }; // XF86MonBrightnessUp\n    key <FK08> { overlay1=<MUTE> };\n    key <FK09> { overlay1=<VOL-> };\n    key <FK10> { overlay1=<VOL+> };\n\n    key <AE01> { overlay1=<FK01> };\n    key <AE02> { overlay1=<FK02> };\n    key <AE03> { overlay1=<FK03> };\n    key <AE04> { overlay1=<FK04> };\n    key <AE05> { overlay1=<FK05> };\n    key <AE06> { overlay1=<FK06> };\n    key <AE07> { overlay1=<FK07> };\n    key <AE08> { overlay1=<FK08> };\n    key <AE09> { overlay1=<FK09> };\n    key <AE10> { overlay1=<FK10> };\n    key <AE11> { overlay1=<FK11> };\n    key <AE12> { overlay1=<FK12> };\n    key <BKSP> { overlay1=<DELE> };\n\n    key <LALT> { overlay1=<CAPS> };\n    key <RALT> { overlay1=<CAPS> };\n\n    // For some strange reason, some Super_R events are triggered when\n    // the Search key is released (i.e. with overlay on).\n    // This maps RWIN to a dummy key (<I253>), to make sure we catch it.\n    key <RWIN> { [ NoSymbol ], overlay1=<I253> };\n\n    // Map dummy key to no symbol\n    key <I253> { [ NoSymbol ] };\n};\nEND\n\nawk '\n# default: print line\n1\n\n# rules/base.ml1_c.part\n/^! model[ \\t]*layout\\[1\\][ \\t]*=[ \\t]*compat$/{\n    found[1]++;\n    print \"  chromebook\t*\t\t=\tcomplete+chromebook\"\n}\n\n# rules/base.ml_c.part\n/^! model[ \\t]*layout[ \\t]*=[ \\t]*compat$/{\n    found[2]++;\n    print \"  chromebook\t*\t\t=\tcomplete+chromebook\"\n}\n\n# rules/evdev.m_s.part\n/^! model[ \\t]*=[ \\t]*symbols$/{\n    found[3]++;\n    print \"  chromebook\t=   +inet(evdev)+chromebook(overlay)\"\n}\n\nEND {\n    for (i = 1; i <= 3; i++) {\n        if (found[i] != 1) {\n            print \"Error: \" FILENAME \": rule \" i \": applied \" found[i] \" times\" > \"/dev/stderr\"\n            exit 1\n        }\n    }\n}\n' \"$XKBROOT/rules/evdev\" > \"$XKBROOTTMP/rules/evdev\"\n\n# rules/base.xml.in\nawk '\n# default: print line\n1\n\n/^.*<modelList>$/{\n    found++;\n    print \"    <model>\";\n    print \"      <configItem>\";\n    print \"        <name>chromebook</name>\";\n    print \"        <description>Chromebook</description>\";\n    print \"        <vendor>Google</vendor>\";\n    print \"      </configItem>\";\n    print \"    </model>\"\n}\n\nEND {\n    if (found != 1) {\n        print \"Error: \" FILENAME \": rule applied \" found \" times\" > \"/dev/stderr\"\n        exit 1\n    }\n}\n' \"$XKBROOT/rules/evdev.xml\" > \"$XKBROOTTMP/rules/evdev.xml\"\n\nawk '\n# default: print line\n1\n\n/^! model$/{\n    found++;\n    print \"  chromebook      Chromebook\"\n}\n\nEND {\n    if (found != 1) {\n        print \"Error: \" FILENAME \": rule applied \" found \" times\" > \"/dev/stderr\"\n        exit 1\n    }\n}\n' \"$XKBROOT/rules/evdev.lst\" > \"$XKBROOTTMP/rules/evdev.lst\"\n\n# Apply the modifications\ncp -a \"$XKBROOTTMP\"/* \"$XKBROOT\"/\n\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    # Hold xkb-data, so that it does not get updated\n    apt-mark hold xkb-data\nfi\n"
  },
  {
    "path": "targets/kodi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ]; then\n    if release -le wheezy; then\n        error 99 \"Kodi not supported on Debian Wheezy or older.\"\n    elif release -eq precise; then\n        error 99 \"Kodi not supported on Precise Ubuntu.\"\n    fi\nfi\nREQUIRES='x11'\nPROVIDES='xbmc'\nDESCRIPTION='Installs the KODI media player. (Approx. 200MB)'\nHOSTBIN='startkodi'\nCHROOTETC='kodi-keyboard.xml kodi-cycle.py'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n\n## Debian Overrides\n# Kodi wiki recommends using the Jessie Backports repository\nif release -eq jessie; then\n    cat > '/etc/apt/sources.list.d/kodi.list' <<EOF\n# Bring in Jessie Backports for newer Kodi\ndeb http://http.debian.net/debian jessie-backports main\nEOF\n    # Update database\n    apt-get update || true\nfi\n\n## Ubuntu Overrides\n# Kodi has an official ppa for the latest stable version\nif [ \"$DISTRO\" = 'ubuntu' ] && [ \"$ARCH\" = 'amd64' -o \"$ARCH\" = 'i386' ]; then    \n    # Pull in the signing key\n    apt-key adv --keyserver 'keyserver.ubuntu.com' \\\n                --recv-keys '91E7EE5E'\n    # Add the software source\n    cat > '/etc/apt/sources.list.d/kodi.list' <<EOF\n# Bring in Kodi PPA Repository\ndeb http://ppa.launchpad.net/team-xbmc/ppa/ubuntu $RELEASE main \ndeb-src http://ppa.launchpad.net/team-xbmc/ppa/ubuntu $RELEASE main \nEOF\n    # Update database\n    apt-get update || true\nfi\n\ninstall kodi pulseaudio\n\n# Configure keymaps kodi for the hotkeys ctr-shift-alt F1/F2 to\n# cycle through chroots/chromeos. We use ~/.kodi/userdata/keymaps/keyboard.xml\n# for this purpose, but the main user may not have been created yet, so we \n# add a script in /etc/profile.d to link ~/.kodi/userdata/keymaps/keyboard.xml\n# to /etc/crouton/kodi-keyboard.xml\n\nprofiledsh='/etc/profile.d/crouton-kodi-keymaps.sh'\n# Make sure symbolic link is setup on login\necho '#!/bin/sh\n\nkeyboardxmldir=\"$HOME/.kodi/userdata/keymaps\"\n# Do not install if user is root, or $HOME does not exist\nif [ \"$(id -u)\" -ne 0 -a -d \"$HOME\" -a ! -e \"$keyboardxmldir/keyboard.xml\" ]; then\n    mkdir -p \"$keyboardxmldir\"\n    ln -sfT /etc/crouton/kodi-keyboard.xml \"$keyboardxmldir/keyboard.xml\"\nfi' > \"$profiledsh\"\n\nchmod 755 \"$profiledsh\"\n\nTIPS=\"$TIPS\nYou can start KODI via the startkodi host command: sudo startkodi\n\"\n"
  },
  {
    "path": "targets/lxde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='gtk-extra'\nDESCRIPTION='Installs the LXDE desktop environment. (Approx. 200MB)'\nHOSTBIN='startlxde'\nCHROOTBIN='crouton-noroot startlxde'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall lxde -- dictionaries-common chromium-browser network-manager wicd\n\n# xenial needs a few more packages.\nif release -ge xenial; then\n    install lxde-common lxsession-logout\nfi\n\nTIPS=\"$TIPS\nYou can start LXDE via the startlxde host command: sudo startlxde\n\"\n"
  },
  {
    "path": "targets/lxde-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='lxde'\nDESCRIPTION='Installs LXDE along with common applications. (Approx. 800MB)'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall ubuntu=lubuntu-desktop,task-lxde-desktop -- network-manager\n"
  },
  {
    "path": "targets/post-common",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This is a target, but it's not selectable by the user. These are the commands\n# that get executed at the very end of the generated prepare.sh.\n# These commands are not optional and are automatically added to targets.\n\nREQUIRES=''\nDESCRIPTION=''\nHOSTBIN='enter-chroot delete-chroot edit-chroot mount-chroot unmount-chroot'\nHOSTBIN=\"$HOSTBIN crash_reporter_wrapper\"\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n\n# Set the default coredump handler\nif [ ! -f '/etc/crouton/core_pattern' ]; then\n    core_pattern=''\n    if [ \"$DISTRO\" = 'ubuntu' ]; then\n        core_pattern='|/usr/share/apport/apport %p %s %c'\n    fi\n    echo \"\\\n# The first non-comment line is the core_pattern that applies to this chroot.\n# See man 5 core for details.\n\n$core_pattern\" > '/etc/crouton/core_pattern'\nfi\n\necho 'Cleaning up...' 1>&2\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    apt-get -y --purge autoremove\n    apt-get clean\nfi\n\n# Add the primary user\ngroups='audio,input,video,wayland,sudo,plugdev,crouton'\nif ! grep -q ':1000:' /etc/passwd; then\n    while ! echo \"$username\" | grep -q '^[a-z][-a-z0-9_]*$'; do\n        if [ -n \"$username\" ]; then\n            echo_tty 'Username must be lowercase letters, numbers, or dashes.'\n        fi\n        echo_tty -n \"$(echo_color \"b\" \"Please specify a username for the primary user: \")\"\n        if [ -n \"$USERNAME\" ]; then\n            username=\"$USERNAME\"\n            echo \"$username\" 1>&2\n        else\n            read -r username junk\n        fi\n    done\n    useradd -u 1000 -G \"$groups\" -s '/bin/bash' -m \"$username\"\n    if [ -t 0 ]; then\n        tries=0\n    else\n        tries=3\n    fi\n    while [ \"$tries\" -lt 3 ] && ! passwd \"$username\"; do\n        tries=\"$((tries+1))\"\n    done\n    if [ \"$tries\" = 3 ]; then\n        echo \\\n\"Password left unset. To set a password, inside the chroot run: passwd $username\" 1>&2\n    fi\nelse\n    username=\"`awk -F: '$3==1000{print $1; exit}' '/etc/passwd'`\"\n    usermod -a -G \"$groups\" \"$username\"\nfi\n\nif [ -n \"$TIPS\" ]; then\n    echo \"\n$(echo_color \"b\" \"Here's some tips:\")\n$TIPS\" 1>&2\nfi\n\nrm -f \"$0\"\n"
  },
  {
    "path": "targets/touch",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ] &&\n        [ \"$DISTRO\" = 'debian' -o \"$DISTRO\" = 'kali' ]; then\n    error 99 \"touch target is not supported on Debian.\"\nfi\nREQUIRES='x11'\nDESCRIPTION='Touchscreen and limited generic gesture support.'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\nif release -lt raring; then\n    # As of 2013-01-30, pre-Raring touchegg is useless. Download it manually.\n    install_mirror_package 'touchegg' 'pool/universe/t/touchegg'\nelse\n    install --minimal touchegg\nfi\n\n# The global config file.\ncat > /etc/touchegg.conf <<EOF\n<touchégg>\n\t<settings>\n\t\t<property name=\"composed_gestures_time\">0</property>\n\t</settings>\n\t<application name=\"All\">\n\t\t<gesture type=\"DRAG\" fingers=\"1\" direction=\"ALL\">\n\t\t\t<action type=\"SCROLL\">SPEED=5:INVERTED=true</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"5\" direction=\"OUT\">\n\t\t\t<action type=\"SHOW_DESKTOP\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"5\" direction=\"UP\">\n\t\t\t<action type=\"SHOW_DESKTOP\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"4\" direction=\"IN\">\n\t\t\t<action type=\"MINIMIZE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"4\" direction=\"OUT\">\n\t\t\t<action type=\"MAXIMIZE_RESTORE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"4\" direction=\"LEFT\">\n\t\t\t<action type=\"MOVE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"5\" direction=\"IN\">\n\t\t\t<action type=\"CLOSE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"5\" direction=\"DOWN\">\n\t\t\t<action type=\"CLOSE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"4\" direction=\"DOWN\">\n\t\t\t<action type=\"MINIMIZE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"4\" direction=\"UP\">\n\t\t\t<action type=\"MAXIMIZE_RESTORE_WINDOW\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"TAP\" fingers=\"3\" direction=\"\">\n\t\t\t<action type=\"MOUSE_CLICK\">BUTTON=2</action>\n\t\t</gesture>\n\t\t<gesture type=\"TAP\" fingers=\"2\" direction=\"\">\n\t\t\t<action type=\"MOUSE_CLICK\">BUTTON=3</action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"3\" direction=\"ALL\">\n\t\t\t<action type=\"CHANGE_DESKTOP\"></action>\n\t\t</gesture>\n\t\t<gesture type=\"TAP\" fingers=\"1\" direction=\"\">\n\t\t\t<action type=\"MOUSE_CLICK\">BUTTON=1</action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"4\" direction=\"RIGHT\">\n\t\t\t<action type=\"MOVE_WINDOW\"></action>\n\t\t</gesture>\n\t</application>\n\t<application name=\"Okular, Gwenview\">\n\t\t<gesture type=\"ROTATE\" fingers=\"2\" direction=\"LEFT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+L</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"IN\">\n\t\t\t<action type=\"SEND_KEYS\">Control+KP_Add</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"OUT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+KP_Subtract</action>\n\t\t</gesture>\n\t\t<gesture type=\"ROTATE\" fingers=\"2\" direction=\"RIGHT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+R</action>\n\t\t</gesture>\n\t</application>\n\t<application name=\"Google-chrome, Firefox, Dolphin, Chromium-browser\">\n\t\t<gesture type=\"DRAG\" fingers=\"2\" direction=\"DOWN\">\n\t\t\t<action type=\"SEND_KEYS\">Control+minus</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"IN\">\n\t\t\t<action type=\"SEND_KEYS\">Control+minus</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"OUT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+equal</action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"2\" direction=\"RIGHT\">\n\t\t\t<action type=\"SEND_KEYS\">Alt+Left</action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"2\" direction=\"UP\">\n\t\t\t<action type=\"SEND_KEYS\">Control+equal</action>\n\t\t</gesture>\n\t\t<gesture type=\"DRAG\" fingers=\"2\" direction=\"LEFT\">\n\t\t\t<action type=\"SEND_KEYS\">Alt+Right</action>\n\t\t</gesture>\n\t</application>\n\t<application name=\"Evince\">\n\t\t<gesture type=\"ROTATE\" fingers=\"2\" direction=\"LEFT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+Right</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"IN\">\n\t\t\t<action type=\"SEND_KEYS\">Control+KP_Add</action>\n\t\t</gesture>\n\t\t<gesture type=\"PINCH\" fingers=\"2\" direction=\"OUT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+KP_Subtract</action>\n\t\t</gesture>\n\t\t<gesture type=\"ROTATE\" fingers=\"2\" direction=\"RIGHT\">\n\t\t\t<action type=\"SEND_KEYS\">Control+Left</action>\n\t\t</gesture>\n\t</application>\n</touchégg>\nEOF\n"
  },
  {
    "path": "targets/unity",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ \"${TARGETNOINSTALL:-c}\" = 'c' ]; then\n    if [ \"$DISTRO\" != 'ubuntu' ]; then\n        error 99 \"unity target is only supported on Ubuntu.\"\n    fi\n\n    if [ \"${ARCH#arm}\" != \"$ARCH\" ] && release -gt precise; then\n        error 99 \"unity is unsupported on ARM for releases after precise.\"\n    fi\nfi\nREQUIRES='gtk-extra'\nDESCRIPTION='Installs the Unity desktop environment. (Approx. 700MB)'\nHOSTBIN='startunity'\nCHROOTBIN='crouton-noroot startunity gnome-session-wrapper crouton-unity-autostart'\nCHROOTETC='unity-autostart.desktop unity-profiled'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n# Note that whitespace is important for syntax.\n# See distropkgs method in installer/prepare.sh\ninstall unity ubuntu-artwork gnome-session nautilus ttf-ubuntu-font-family \\\n        pulseaudio \\\n        ubuntu~precise=unity-2d, \\\n        ubuntu~precise=,ubuntu-settings \\\n        ubuntu~precise=,ubuntu-session \\\n        -- network-manager brasero firefox\n\n# XDG autostart/profile.d additions only needed in saucy and later\nif release -ge saucy; then\n    autostartdir='/etc/xdg/autostart'\n    # Remove previous indicator-only desktop file\n    rm -f \"$autostartdir\"/crouton-unity-indicator.desktop\n\n    # Set up global autostart script\n    mkdir -p \"$autostartdir\"\n    ln -sfT /etc/crouton/unity-autostart.desktop \\\n        \"$autostartdir\"/crouton-unity-autostart.desktop\n\n    # Set up profile.d\n    chmod 755 /etc/crouton/unity-profiled\n    ln -sfT /etc/crouton/unity-profiled /etc/profile.d/crouton-unity-profiled.sh\nfi\n\nTIPS=\"$TIPS\nYou can start Unity via the startunity host command: sudo startunity\n\"\n"
  },
  {
    "path": "targets/unity-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='unity'\nDESCRIPTION='Installs Unity along with common applications. (Approx. 1100MB)'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\ninstall ubuntu-desktop -- network-manager xorg\n"
  },
  {
    "path": "targets/x11",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nif [ -z \"$TARGETNOINSTALL\" ]; then\n    # Wheezy/Kali are too old to use KMS well\n    if [ -f /sbin/frecon ] && release -le wheezy -le kali; then\n        REQUIRES='xiwi'\n    else\n        REQUIRES='xorg'\n    fi\nfi\nDESCRIPTION='X11 via autodetected backend. Does not install any desktop environment.'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n"
  },
  {
    "path": "targets/x11-common",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This isn't a real target; it's the common commands for installing various\n# forms of X11.\n\n### Append to prepare.sh:\n\n# Create dummy xserver-xorg-legacy\ninstall_dummy xserver-xorg-legacy\n\n# Store and apply the X11 method\nln -sfT '/etc/crouton/xserverrc' '/etc/X11/xinit/xserverrc'\necho \"$XMETHOD\" > '/etc/crouton/xmethod'\n\n# Remove old vgem hack\noffset=\"`grep -F -m 1 -boa 'croutonhax' /usr/bin/Xorg 2>/dev/null || true`\"\nif [ -n \"$offset\" ]; then\n    echo -n 'card[0-9]*' | dd seek=\"${offset%:*}\" bs=1 of=/usr/bin/Xorg \\\n        conv=notrunc,nocreat 2>/dev/null\nfi\n\n# Install utilities and links for powerd-poking daemon\ncompile xi2event '-lX11 -lXi' libx11-dev libxi-dev\ninstall --minimal dbus xdg-utils\nln -sf croutonpowerd /usr/local/bin/gnome-screensaver-command\nln -sf croutonpowerd /usr/local/bin/xscreensaver-command\n\n# Install nicer cursors\ninstall --minimal dmz-cursor-theme\n\n# Install bsdmainutils, xbindkeys and xautomation for shortcuts\ninstall --minimal bsdmainutils xbindkeys xautomation\n\n# We need chvt on freon systems\nif [ -f \"/sys/class/tty/tty0/active\" ]; then\n    install --minimal kbd\n    # Allow users to run sudo chvt without password, so we don't need to run\n    # croutoncycle as root\n    echo '%sudo ALL = NOPASSWD:/bin/chvt' > /etc/sudoers.d/chvt\n    chmod 440 /etc/sudoers.d/chvt\nfi\n\n# Add a blank Xauthority to all users' home directories\ntouch /etc/skel/.Xauthority\nchmod 600 /etc/skel/.Xauthority\n\n# Make xscreensaver default to blank only\necho 'mode: blank' > /etc/skel/.xscreensaver\n\n# Create /usr/share/desktop-directories to avoid issues with xdg-desktop-menu\nmkdir -p /usr/share/desktop-directories\n\n# Prevent Upstart from taking over X sessions\nif release -eq trusty -eq xenial; then\n    dpkg-divert --local --rename --add /etc/upstart-xsessions\nfi\n\n# FIXME: is this necessary for rootless X11?\n# This makes sure Xephyr, running as user, can write server-*.xkm files to\n# /var/lib/xkb, so it does not conflict with files created by Chromium OS\n# X server in /tmp.\nmkdir -p /var/lib/xkb\nchgrp video /var/lib/xkb\nchmod g+rw /var/lib/xkb\n\n# Update policies for mounting and unmounting devices with udisks2\nmkdir -p '/etc/polkit-1/localauthority/10-vendor.d'\ncat > '/etc/polkit-1/localauthority/10-vendor.d/10-crouton-udisks2.pkla' <<EOF\n[Allow mounting when started from crosh]\nIdentity=*\nAction=org.freedesktop.udisks*.filesystem-mount\nResultAny=auth_admin\nResultInactive=yes\nResultActive=yes\n\n[Allow eject when started from crosh]\nIdentity=*\nAction=org.freedesktop.udisks*.*eject*\nResultAny=auth_admin\nResultInactive=yes\nResultActive=yes\n\n[Disallow unmounting of Chromium OS mounts; it won't work]\nIdentity=*\nAction=org.freedesktop.udisks*.filesystem-unmount-others\nResultAny=no\nResultInactive=no\nResultActive=no\nEOF\n"
  },
  {
    "path": "targets/xbmc",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='kodi'\nDESCRIPTION=''\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n# This placeholder target is here to provide a migration path from xbmc to kodi.\n"
  },
  {
    "path": "targets/xfce",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='gtk-extra'\nDESCRIPTION='Installs the Xfce desktop environment. (Approx. 250MB)'\nHOSTBIN='startxfce4'\nCHROOTBIN='crouton-noroot startxfce4'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n# Ubuntu 12.04 no longer has a PPA for Xfce 4.10 - so remove it if found.\nif release -eq precise && [ \"$ARCH\" = 'amd64' -o \"$ARCH\" = 'i386' ]; then\n    if [ -s /etc/apt/sources.list.d/xfce-4.10.list ]; then\n        rm -f /etc/apt/sources.list.d/xfce-4.10.list*\n        # Update database\n        apt-get update || true\n    fi\nfi\n\n# Fix icons by setting default icon theme to Tango\nmkdir -p /etc/skel/.config/xfce4/xfconf/xfce-perchannel-xml\ncat > '/etc/skel/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml' <<EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<channel name=\"xsettings\" version=\"1.0\">\n  <property name=\"Net\" type=\"empty\">\n    <property name=\"IconThemeName\" type=\"string\" value=\"Tango\"/>\n  </property>\n</channel>\nEOF\n\nif release -lt xenial && [ \"$ARCH\" = 'arm64' ]; then\n    install_dummy xfce4-battery-plugin\nfi\n\ninstall xfce4 xfce4-goodies dbus-x11 ubuntu=shimmer-themes, \\\n        -- hddtemp xorg\n\nTIPS=\"$TIPS\nYou can start Xfce via the startxfce4 host command: sudo startxfce4\n\"\n"
  },
  {
    "path": "targets/xfce-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='xfce'\nDESCRIPTION='Installs Xfce along with common applications. (Approx. 1200MB)'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\n# Avoid /etc/skel/.xscreensaver conflicts\nxsvr='/etc/skel/.xscreensaver'\n[ -f \"$xsvr\" ] && mv -f \"$xsvr\" \"$xsvr.bak\"\ninstall ubuntu=xubuntu-desktop,task-xfce-desktop -- network-manager xorg\n[ -f \"$xsvr.bak\" ] && mv -f \"$xsvr.bak\" \"$xsvr\"\n"
  },
  {
    "path": "targets/xiwi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='audio extension'\nPROVIDES='x11'\nDESCRIPTION='X.org X11 backend running unaccelerated in a Chromium OS window.'\nHOSTBIN='startxiwi'\nCHROOTBIN='croutoncycle croutonfindnacl croutontriggerd croutonxinitrc-wrapper setres xinit xiwi'\nCHROOTETC='xbindkeysrc.scm xiwi.conf xorg-dummy.conf xserverrc xserverrc-xiwi xserverrc-local.example'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\nXMETHOD=\"${XMETHOD:-xiwi}\"\n\nltspackages=''\n# On non-ARM precise, install lts-trusty xorg server for compatibility with xorg\n# if kernel version != 3.4 (lts-trusty mesa requires version >=3.6)\nif [ \"${ARCH#arm}\" = \"$ARCH\" ] && release -eq precise \\\n        && ! uname -r | grep -q \"^3.4.\"; then\n    # We still install xorg later to pull in its dependencies\n    ltspackages='-lts-trusty'\n    install --minimal \"xserver-xorg$ltspackages\" \"libgl1-mesa-glx$ltspackages\"\nfi\n\n# Unhold xserver-xorg-video-dummy to make sure deps are pulled in\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    apt-mark unhold xserver-xorg-video-dummy$ltspackages || true 2>/dev/null\nfi\n\ninstall xorg xserver-xorg-video-dummy$ltspackages\ninstall --minimal i3\n\n# Remove some unsupported options on old versions of i3\nif release -le precise; then\n     sed -i '/#.*#.*#.*#/s/ #......$//;/--release/d' /etc/crouton/xiwi.conf\nfi\n\n# Compile croutonfbserver\ncompile fbserver '-lX11 -lXfixes -lXdamage -lXext -lXtst' \\\n    libx11-dev libxfixes-dev libxdamage-dev libxext-dev libxtst-dev\ncompile findnacld ''\n\nln -sf /etc/crouton/xorg-dummy.conf /etc/X11/\n\n# Download the latest xf86-video-dummy package\nurlbase=\"https://xorg.freedesktop.org/releases/individual/driver\"\n\nDUMMYBUILDTMP=\"`mktemp -d crouton-cras.XXXXXX --tmpdir=/tmp`\"\n\naddtrap \"rm -rf --one-file-system '$DUMMYBUILDTMP'\"\n\necho \"Download Xorg dummy driver...\" 1>&2\n\nsuperinsecure=''\nif release -le wheezy; then\n     # Wheezy doesn't have the right CA in ca-certificates that validates x.org\n     superinsecure='--no-check-certificate'\nfi\nwget $superinsecure -O \"$DUMMYBUILDTMP/dummy.tar.gz\" \\\n     \"$urlbase/xf86-video-dummy-0.3.8.tar.gz\"\n\ninstall --minimal --asdeps patch gcc libc-dev pkg-config \\\n    xserver-xorg-dev$ltspackages x11proto-xf86dga-dev\n\n(\n    cd \"$DUMMYBUILDTMP\"\n    # -m prevents \"time stamp is in the future\" spam\n    tar --strip-components=1 -xmf dummy.tar.gz\n\n    echo \"Patching Xorg dummy driver (xrandr 1.2 support)...\" 1>&2\n    patch -p1 <<EOF\ndiff --git a/src/dummy_driver.c b/src/dummy_driver.c\nindex 2656602..0718e1f 100644\n--- a/src/dummy_driver.c\n+++ b/src/dummy_driver.c\n@@ -34,6 +34,8 @@\n #include <X11/extensions/Xv.h>\n #endif\n \n+#include \"xf86Crtc.h\"\n+\n /*\n  * Driver data structures.\n  */\n@@ -175,6 +177,115 @@ dummySetup(pointer module, pointer opts, int *errmaj, int *errmin)\n #endif /* XFree86LOADER */\n \n static Bool\n+size_valid(ScrnInfoPtr pScrn, int width, int height)\n+{\n+    /* Guard against invalid parameters */\n+    if (width == 0 || height == 0 ||\n+        width > DUMMY_MAX_WIDTH || height > DUMMY_MAX_HEIGHT)\n+        return FALSE;\n+\n+    /* videoRam is in kb, divide first to avoid 32-bit int overflow */\n+    if ((width*height+1023)/1024*pScrn->bitsPerPixel/8 > pScrn->videoRam)\n+        return FALSE;\n+\n+    return TRUE;\n+}\n+\n+static Bool\n+dummy_xf86crtc_resize(ScrnInfoPtr pScrn, int width, int height)\n+{\n+    int old_width, old_height;\n+\n+    old_width = pScrn->virtualX;\n+    old_height = pScrn->virtualY;\n+\n+    if (size_valid(pScrn, width, height)) {\n+        PixmapPtr rootPixmap;\n+        ScreenPtr pScreen = pScrn->pScreen;\n+\n+        pScrn->virtualX = width;\n+        pScrn->virtualY = height;\n+\n+        rootPixmap = pScreen->GetScreenPixmap(pScreen);\n+        if (!pScreen->ModifyPixmapHeader(rootPixmap, width, height,\n+                                         -1, -1, -1, NULL)) {\n+            pScrn->virtualX = old_width;\n+            pScrn->virtualY = old_height;\n+            return FALSE;\n+        }\n+\n+        pScrn->displayWidth = rootPixmap->devKind /\n+            (rootPixmap->drawable.bitsPerPixel / 8);\n+\n+        return TRUE;\n+    } else {\n+        return FALSE;\n+    }\n+}\n+\n+static const xf86CrtcConfigFuncsRec dummy_xf86crtc_config_funcs = {\n+    dummy_xf86crtc_resize\n+};\n+\n+static xf86OutputStatus\n+dummy_output_detect(xf86OutputPtr output)\n+{\n+    return XF86OutputStatusConnected;\n+}\n+\n+static int\n+dummy_output_mode_valid(xf86OutputPtr output, DisplayModePtr pMode)\n+{\n+    if (size_valid(output->scrn, pMode->HDisplay, pMode->VDisplay)) {\n+        return MODE_OK;\n+    } else {\n+        return MODE_MEM;\n+    }\n+}\n+\n+static DisplayModePtr\n+dummy_output_get_modes(xf86OutputPtr output)\n+{\n+    return NULL;\n+}\n+\n+static void\n+dummy_output_dpms(xf86OutputPtr output, int dpms)\n+{\n+    return;\n+}\n+\n+static const xf86OutputFuncsRec dummy_output_funcs = {\n+    .detect = dummy_output_detect,\n+    .mode_valid = dummy_output_mode_valid,\n+    .get_modes = dummy_output_get_modes,\n+    .dpms = dummy_output_dpms,\n+};\n+\n+static Bool\n+dummy_crtc_set_mode_major(xf86CrtcPtr crtc, DisplayModePtr mode,\n+\t\t\t  Rotation rotation, int x, int y)\n+{\n+    crtc->mode = *mode;\n+    crtc->x = x;\n+    crtc->y = y;\n+    crtc->rotation = rotation;\n+\n+    return TRUE;\n+}\n+\n+static void\n+dummy_crtc_dpms(xf86CrtcPtr output, int dpms)\n+{\n+    return;\n+}\n+\n+static const xf86CrtcFuncsRec dummy_crtc_funcs = {\n+    .set_mode_major = dummy_crtc_set_mode_major,\n+    .dpms = dummy_crtc_dpms,\n+};\n+\n+static Bool\n DUMMYGetRec(ScrnInfoPtr pScrn)\n {\n     /*\n@@ -280,6 +391,8 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags)\n     DUMMYPtr dPtr;\n     int maxClock = 300000;\n     GDevPtr device = xf86GetEntityInfo(pScrn->entityList[0])->device;\n+    xf86OutputPtr output;\n+    xf86CrtcPtr crtc;\n \n     if (flags & PROBE_DETECT) \n \treturn TRUE;\n@@ -343,13 +456,6 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags)\n     if (!xf86SetDefaultVisual(pScrn, -1)) \n \treturn FALSE;\n \n-    if (pScrn->depth > 1) {\n-\tGamma zeros = {0.0, 0.0, 0.0};\n-\n-\tif (!xf86SetGamma(pScrn, zeros))\n-\t    return FALSE;\n-    }\n-\n     xf86CollectOptions(pScrn, device->options);\n     /* Process the options */\n     if (!(dPtr->Options = malloc(sizeof(DUMMYOptions))))\n@@ -379,64 +485,45 @@ DUMMYPreInit(ScrnInfoPtr pScrn, int flags)\n \t\t   maxClock);\n     }\n \n-    pScrn->progClock = TRUE;\n-    /*\n-     * Setup the ClockRanges, which describe what clock ranges are available,\n-     * and what sort of modes they can be used for.\n-     */\n-    clockRanges = (ClockRangePtr)xnfcalloc(sizeof(ClockRange), 1);\n-    clockRanges->next = NULL;\n-    clockRanges->ClockMulFactor = 1;\n-    clockRanges->minClock = 11000;   /* guessed §§§ */\n-    clockRanges->maxClock = maxClock;\n-    clockRanges->clockIndex = -1;\t\t/* programmable */\n-    clockRanges->interlaceAllowed = TRUE; \n-    clockRanges->doubleScanAllowed = TRUE;\n-\n-    /* Subtract memory for HW cursor */\n-\n-\n-    {\n-\tint apertureSize = (pScrn->videoRam * 1024);\n-\ti = xf86ValidateModes(pScrn, pScrn->monitor->Modes,\n-\t\t\t      pScrn->display->modes, clockRanges,\n-\t\t\t      NULL, 256, DUMMY_MAX_WIDTH,\n-\t\t\t      (8 * pScrn->bitsPerPixel),\n-\t\t\t      128, DUMMY_MAX_HEIGHT, pScrn->display->virtualX,\n-\t\t\t      pScrn->display->virtualY, apertureSize,\n-\t\t\t      LOOKUP_BEST_REFRESH);\n-\n-       if (i == -1)\n-           RETURN;\n-    }\n+    xf86CrtcConfigInit(pScrn, &dummy_xf86crtc_config_funcs);\n+\n+    xf86CrtcSetSizeRange(pScrn, 256, 256, DUMMY_MAX_WIDTH, DUMMY_MAX_HEIGHT);\n+\n+    crtc = xf86CrtcCreate(pScrn, &dummy_crtc_funcs);\n+\n+    output = xf86OutputCreate (pScrn, &dummy_output_funcs, \"default\");\n \n-    /* Prune the modes marked as invalid */\n-    xf86PruneDriverModes(pScrn);\n+    output->possible_crtcs = 0x7f;\n\n-    if (i == 0 || pScrn->modes == NULL) {\n+    xf86InitialConfiguration(pScrn, TRUE);\n+\n+    if (pScrn->depth > 1) {\n+\tGamma zeros = {0.0, 0.0, 0.0};\n+\n+\tif (!xf86SetGamma(pScrn, zeros))\n+\t    return FALSE;\n+    }\n+\n+    if (pScrn->modes == NULL) {\n \txf86DrvMsg(pScrn->scrnIndex, X_ERROR, \"No valid modes found\\n\");\n \tRETURN;\n     }\n \n-    /*\n-     * Set the CRTC parameters for all of the modes based on the type\n-     * of mode, and the chipset's interlace requirements.\n-     *\n-     * Calling this is required if the mode->Crtc* values are used by the\n-     * driver and if the driver doesn't provide code to set them.  They\n-     * are not pre-initialised at all.\n-     */\n-    xf86SetCrtcForModes(pScrn, 0); \n- \n     /* Set the current mode to the first in the list */\n     pScrn->currentMode = pScrn->modes;\n \n-    /* Print the list of modes being used */\n-    xf86PrintModes(pScrn);\n+    /* Set default mode in CRTC */\n+    crtc->funcs->set_mode_major(crtc, pScrn->currentMode, RR_Rotate_0, 0, 0);\n \n     /* If monitor resolution is set on the command line, use it */\n     xf86SetDpi(pScrn, 0, 0);\n \n+    /* Set monitor size based on DPI */\n+    output->mm_width = pScrn->xDpi > 0 ?\n+        (pScrn->virtualX * 254 / (10*pScrn->xDpi)) : 0;\n+    output->mm_height = pScrn->yDpi > 0 ?\n+        (pScrn->virtualY * 254 / (10*pScrn->yDpi)) : 0;\n+\n     if (xf86LoadSubModule(pScrn, \"fb\") == NULL) {\n \tRETURN;\n     }\n@@ -537,6 +624,8 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL)\n \n     if (!miSetPixmapDepths ()) return FALSE;\n \n+    pScrn->displayWidth = pScrn->virtualX;\n+\n     /*\n      * Call the framebuffer layer's ScreenInit function, and fill in other\n      * pScreen fields.\n@@ -575,23 +664,6 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL)\n     if (dPtr->swCursor)\n \txf86DrvMsg(pScrn->scrnIndex, X_CONFIG, \"Using Software Cursor.\\n\");\n \n-    {\n-\n-\t \n-\tBoxRec AvailFBArea;\n-\tint lines = pScrn->videoRam * 1024 /\n-\t    (pScrn->displayWidth * (pScrn->bitsPerPixel >> 3));\n-\tAvailFBArea.x1 = 0;\n-\tAvailFBArea.y1 = 0;\n-\tAvailFBArea.x2 = pScrn->displayWidth;\n-\tAvailFBArea.y2 = lines;\n-\txf86InitFBManager(pScreen, &AvailFBArea); \n-\t\n-\txf86DrvMsg(pScrn->scrnIndex, X_INFO, \n-\t\t   \"Using %i scanlines of offscreen memory \\n\"\n-\t\t   , lines - pScrn->virtualY);\n-    }\n-\n     xf86SetBackingStore(pScreen);\n     xf86SetSilkenMouse(pScreen);\n \t\n@@ -618,6 +690,9 @@ DUMMYScreenInit(SCREEN_INIT_ARGS_DECL)\n \t\t\t     | CMAP_RELOAD_ON_MODE_SWITCH))\n \treturn FALSE;\n \n+    if (!xf86CrtcScreenInit(pScreen))\n+        return FALSE;\n+\n /*     DUMMYInitVideo(pScreen); */\n \n     pScreen->SaveScreen = DUMMYSaveScreen;\nEOF\n\n    # Fake version 0.3.9\n    package=\"xf86-video-dummy\"\n    major='0'\n    minor='3'\n    patch='9'\n    version=\"$major.$minor.$patch\"\n\n    sed -e '\n        s/#undef \\(HAVE_.*\\)$/#define \\1 1/\n        s/#undef \\(USE_.*\\)$/#define \\1 1/\n        s/#undef \\(STDC_HEADERS\\)$/#define \\1 1/\n        s/#undef \\(.*VERSION\\)$/#define \\1 \"'$version'\"/\n        s/#undef \\(.*VERSION_MAJOR\\)$/#define \\1 '$major'/\n        s/#undef \\(.*VERSION_MINOR\\)$/#define \\1 '$minor'/\n        s/#undef \\(.*VERSION_PATCHLEVEL\\)$/#define \\1 '$patch'/\n        s/#undef \\(.*PACKAGE_STRING\\)$/#define \\1 \"'\"$package $version\"'\"/\n        s/#undef \\(.*PACKAGE_*\\)$/#define \\1 \"'$package'\"/\n    ' config.h.in > config.h\n\n    echo \"Compiling Xorg dummy driver...\" 1>&2\n\n    cd src\n    # Convert Makefile.am to a shell script, and run it.\n    {\n        echo '\n            DGA=1\n            CFLAGS=\"-std=gnu99 -O2 -g -DHAVE_CONFIG_H -I.. -I.\"\n            XORG_CFLAGS=\"'\"`pkg-config --cflags xorg-server`\"'\"\n        '\n\n        convert_automake\n\n        echo '\n            buildlib dummy_drv\n        '\n    } | sh -s -e $SETOPTIONS\n\n    echo \"Installing Xorg dummy driver...\" 1>&2\n\n    DRIVERDIR=\"/usr/lib/xorg/modules/drivers\"\n    mkdir -p \"$DRIVERDIR/\"\n    /usr/bin/install -s dummy_drv.so \"$DRIVERDIR/\"\n) # End compilation subshell\n\nif [ \"${DISTROAKA:-\"$DISTRO\"}\" = 'debian' ]; then\n    # Hold xserver-xorg-video-dummy to make sure the driver does not get erased\n    apt-mark hold xserver-xorg-video-dummy$ltspackages\nfi\n\nTIPS=\"$TIPS\"'\nYou can open your running chroot desktops by clicking on the extension icon.\nOnce in a crouton window, press fullscreen or the \"switch window\" key to switch\nback to Chromium OS.\n\nYou can launch individual apps in crouton windows by using the \"xiwi\" command\nin the chroot shell. Use startxiwi to launch directly from the host shell.\nUse the startxiwi parameter -b to run in the background.\nExample: sudo startxiwi -b xterm\n'\n\n### append x11-common\n"
  },
  {
    "path": "targets/xorg",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\nREQUIRES='audio'\nPROVIDES='x11'\nDESCRIPTION='X.Org X11 backend. Enables GPU acceleration on supported platforms.'\nCHROOTBIN='croutoncycle croutontriggerd croutonxinitrc-wrapper setres xinit'\nCHROOTETC='xbindkeysrc.scm xorg-intel-sna.conf xserverrc xserverrc-xorg xserverrc-local.example'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n\n### Append to prepare.sh:\nXMETHOD=\"${XMETHOD:-xorg}\"\n\n# Migration from ambiguous XMETHOD\nrm -f '/etc/crouton/xserverrc-x11'\n\n\n# Figure out what we need on this system\n\n# Freon?\nfreon=''\nif [ ! -f \"/sys/class/tty/tty0/active\" ]; then\n    freon='y'\nfi\n\n# Pull in backported Xorg when possible on precise to support newer hardware\nbackport=''\nif release -eq precise; then\n    if [ \"${ARCH#arm}\" = \"$ARCH\" ]; then\n        # Note: lts-trusty mesa requires kernel version >=3.6\n        if ! uname -r | grep -q \"^3.4.\"; then\n            backport='-lts-trusty'\n        fi\n    else\n        # ARM only offers quantal backport at the moment\n        backport='-lts-quantal'\n    fi\nfi\n\n# Intel?\ninteldriver=''\nintelhasfbc=''\npinmesa=''\nintelfbcsupport='y'\nif [ \"${ARCH#arm}\" = \"$ARCH\" ]; then\n    inteldriver=\"xserver-xorg-video-intel$backport\"\n\n    # If the system has an Intel FBC-capable video card but has too old a driver\n    # to support the chipset, we'll need to disable hardware acceleration.\n    if grep -q 1 '/sys/module/i915/parameters/i915_enable_fbc' 2>/dev/null; then\n        intelhasfbc=y\n        if release -le wheezy -le sana; then\n            intelfbcsupport=''\n        fi\n    fi\n\n    # On saucy onwards, if kernel version is 3.4, manually pin down old mesa\n    # libraries, as new ones require version >=3.6 (see issue #704).\n    # This is only required on non-Atom Intel chipsets (Atom uses i915 dri driver)\n    if release -ge saucy && uname -r | grep -q \"^3.4.\" &&\n            ! grep -q 0xa0 /sys/class/graphics/fb0/device/device; then\n        pinmesa='y'\n    fi\n\n    # Unhold xserver-xorg-video-intel, modification will be reapplied later.\n    apt-mark unhold \"$inteldriver\" 2>/dev/null || true\nfi\n\n# Catalog relevant and irrelevant video drivers\nfbdev=\"xserver-xorg-video-fbdev$backport\"\n\nif release -lt sid -lt kali-rolling -lt vivid; then\n    modesetting=\"xserver-xorg-video-modesetting$backport\"\nelse\n    # modesetting is built into xserver-xorg-core\n    modesetting=''\nfi\n\ninstallvideodrivers=''\nremovevideodrivers=''\nif [ -n \"$freon\" ]; then\n    installvideodrivers=\"$modesetting\"\n    removevideodrivers=\"$fbdev\"\nelse\n    installvideodrivers=\"$fbdev\"\nfi\nif [ -n \"$inteldriver\" ]; then\n    # If we need SNA (for fbc) but don't have it, don't install intel\n    if [ -z \"$intelhasfbc\" -o -n \"$intelfbcsupport\" ]; then\n        installvideodrivers=\"$installvideodrivers $inteldriver\"\n    else\n        removevideodrivers=\"$removevideodrivers $inteldriver\"\n    fi\nfi\n\n\n# On Freon, we need crazy xorg hacks\nif [ -n \"$freon\" ]; then\n    compile freon '-ldl -ldrm -I/usr/include/libdrm' so libdrm-dev\nfi\n\n# Pin precise's version of mesa if necessary\npinfile='/etc/apt/preferences.d/precise-mesa-pin'\npinlist='/etc/apt/sources.list.d/precise.list'\npindummypkg='libwayland-egl1-dummy'\nif [ -n \"$pinmesa\" ]; then\n    # Create a dummy libwayland-egl1 package, to satisfy dependencies\n    # (the libraries are actually provided by libegl1-mesa-drivers in precise)\n    install --minimal --asdeps equivs\n\n    DEBTMP=\"`mktemp -d crouton.XXXXXX --tmpdir=/tmp`\"\n    addtrap \"rm -rf --one-file-system '$DEBTMP'\"\n\n    ( cd \"$DEBTMP\"; equivs-build - ) <<END\nSection: misc\nPriority: optional\nStandards-Version: 3.9.2\n\nPackage: $pindummypkg\nVersion: 8.0.4\nDepends: libegl1-mesa-drivers\nProvides: libwayland-egl1\nDescription: Dummy package providing libwayland-egl1\nEND\n\n    # Add precise package sources\n    mirror=\"`detect_mirror`\"\n\n    cat > \"$pinlist\" <<END\ndeb $mirror precise main\ndeb $mirror precise-updates main\ndeb $mirror precise-security main\nEND\n\n    cat > \"$pinfile\" <<END\n# Do not install any packages from precise by default\nPackage: *\nPin: release n=precise\nPin-Priority: -10\n\n# Install mesa packages and their dependencies from precise\nPackage: libegl1-mesa:* libegl1-mesa-dbg:* libegl1-mesa-dev:* \\\n         libegl1-mesa-drivers:* libegl1-mesa-drivers-dbg:* \\\n         libgl1-mesa-dev:* libgl1-mesa-dri:* libgl1-mesa-dri-dbg:* \\\n         libgl1-mesa-glx:* libgl1-mesa-glx-dbg:* \\\n         libglapi-mesa:* libglapi-mesa-dbg:* \\\n         libgles1-mesa:* libgles1-mesa-dbg:* libgles1-mesa-dev:* \\\n         libgles2-mesa:* libgles2-mesa-dbg:* libgles2-mesa-dev:* \\\n         libdrm-nouveau1a:* libllvm3.0:* libudev0:*\nPin: release n=precise\nPin-Priority: 1100\nEND\n\n    # Fetch new package sources\n    apt-get update || true\n\n    # Forcibly replace libwayland-egl1-mesa by libwayland-egl1-dummy\n    # (dpkg -r does not fail if package does not exist)\n    dpkg -r --force-depends libwayland-egl1-mesa libwayland-egl1-mesa:i386\n    dpkg -i --force-depends \"$DEBTMP\"/libwayland-egl1-dummy_*_all.deb\n\n    # Downgrade packages, -f is needed as dependencies are broken\n    # This happens to install python2.7-minimal as well, pulled in by a wrong\n    # dependency of ubuntu-minimal-1.267 (precise), which we did not install.\n    # Looks like an apt bug, I guess we can live with that.\n    apt-get -y --force-yes --no-install-recommends dist-upgrade -f\nelif [ -f \"$pinfile\" -o -f \"$pinlist\" ]; then\n    # No longer need the pin; remove it\n    rm -f \"$pinfile\" \"$pinlist\"\n    apt-get update || true\n    dpkg -r --force-depends \"$pindummypkg\"\n    apt-get -y --force-yes --no-install-recommends dist-upgrade -f\nfi\n\n# Install backported xorg instead of the default one\nif [ -n \"$backport\" ]; then\n    # We still install xorg later to pull in its dependencies\n    install --minimal \"xserver-xorg$backport\" \"libgl1-mesa-glx$backport\" \\\n        \"libegl1-mesa$backport\" \"libgles2-mesa$backport\" \\\n        \"xserver-xorg-input-synaptics$backport\" $installvideodrivers\nfi\n\nif release -lt stretch -lt kali-rolling -lt yakkety; then\n    vmmouse='xserver-xorg-input-vmmouse'\nelse\n    # xserver-xorg-input-vmmouse is obsolete\n    vmmouse=''\nfi\n\n# Install xorg, no video drivers\nif [ -z \"$inteldriver\" -a -n \"$backport\" ] && release -eq precise; then\n    # xorg is packaged wrong on ARM and conflicts with xserver-xorg-lts-quantal\n    # so replace it with something not broken\n    install_dummy xorg -- xserver-xorg$backport libgl1-mesa-glx$backport \\\n        libgl1-mesa-dri$backport libglu1-mesa xfonts-base x11-apps \\\n        x11-session-utils x11-utils x11-xfs-utils x11-xkb-utils \\\n        x11-xserver-utils xauth xinit xfonts-utils xkb-data xorg-docs-core \\\n        xterm x11-common xinput\nelse\n    install xorg $installvideodrivers -- xserver-xorg-video-all$backport \\\n        $vmmouse\nfi\n\n# Remove bad video drivers\nif [ -n \"$removevideodrivers\" ]; then\n    remove $removevideodrivers\nfi\n\n# If this is a system with framebuffer compression, we need SNA+tearfree\nxorgconf='/usr/share/X11/xorg.conf.d/20-crouton-intel-sna.conf'\nif [ -n \"$intelhasfbc\" -a -n \"$intelfbcsupport\" ]; then\n    mkdir -p \"${xorgconf%/*}\"\n    ln -sfT /etc/crouton/xorg-intel-sna.conf \"$xorgconf\"\nelse\n    # In case this got moved to a different system, delete the config\n    rm -f \"$xorgconf\"\nfi\n\n# Fix launching X11 from inside crosh (user doesn't own a TTY)\necho 'allowed_users=anybody' > '/etc/X11/Xwrapper.config'\n\ninteldrv='/usr/lib/xorg/modules/drivers/intel_drv.so'\nif [ -n \"$freon\" -a -e \"$inteldrv\" ]; then\n    # Modify the Intel driver to call getuid0 (provided by croutonfreon.so)\n    # instead of geteuid, to force DRM master setting/dropping.\n    offset=\"`grep -F -m 1 -boa 'geteuid' \"$inteldrv\" 2>/dev/null || true`\"\n    if [ -n \"$offset\" ]; then\n        echo -n 'getuid0' | dd seek=\"${offset%:*}\" bs=1 of=\"$inteldrv\" \\\n                               conv=notrunc,nocreat 2>/dev/null\n    fi\n\n    if grep -F -q -a 'getuid0' \"$inteldrv\" 2>/dev/null; then\n        # Hold xserver-xorg-video-intel to make sure that the modification\n        # (which may have been made in a previous update) does not get erased.\n        apt-mark hold \"$inteldriver\"\n    fi\nfi\n\nTIPS=\"$TIPS\nYou can flip through your running chroot desktops and Chromium OS by hitting\nCtrl+Alt+Shift+Back and Ctrl+Alt+Shift+Forward.\n\"\n\n### append x11-common\n"
  },
  {
    "path": "test/autotest_control.template",
    "content": "# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nfrom autotest_lib.client.common_lib import utils\n\nAUTHOR = \"drinkcat,dnschneid\"\nNAME = \"platform_Crouton_wrapper\"\nTIME = \"LENGTHY\"\nTEST_TYPE = \"client\"\n\nDOC = \"\"\"\nThis test is a wrapper around platform_Crouton, meant to be dynamically modified\nand passed to atest.\n\nThis test fetches a specific branch of crouton, and runs crouton tests.\n\n@param repo: (dnschneid/crouton) github repository to fetch from\n@param branch: (master) github branch\n@param runargs: (-R precise 00) parameters to pass to run.sh\n@param env: () Environment variables to set, semicolon-separated key=val pairs.\n            Only CROUTON_MIRROR_* can be set\n\"\"\"\n\n# For debugging purpose\nutils.system('cat /etc/lsb-release')\n\nargs_dict = {\n          'repo': \"\"\"###REPO###\"\"\",\n          'branch': \"\"\"###BRANCH###\"\"\",\n          'runargs': \"\"\"###RUNARGS###\"\"\",\n          'env': \"\"\"###ENV###\"\"\"\n}\n\njob.run_test('platform_Crouton', args=args_dict)\n"
  },
  {
    "path": "test/daemon.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Monitors a web-based CSV queue for autotest requests, runs the test, and\n# uploads the status and results.\n\n# Example CSV contents (must be fully quoted, no double-quotes in content):\n#   \"Timestamp\",\"Repository\",\"Branch\",\"Additional parameters\",\"Run type\"\n#   \"2013/10/16 8:24:52 PM GMT\",\"dnschneid/crouton\",\"master\",\"\",\"FULL\"\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nSCRIPTDIR=\"`readlink -f -- \"\\`dirname \"$0\"\\`/..\"`\"\n# Poll queue file every x seconds\nPOLLINTERVAL=10\n# Full sync status at least every x seconds\nLOGUPLOADINTERVAL=60\n# After the end of a test, try to fetch results for x seconds\nFETCHTIMEOUT=7200\n# After the end of a test, do a second fetch after x seconds\nREFETCHTIME=300\n# Archive every hour, files older than 7 days\nARCHIVEINTERVAL=3600\nARCHIVEMAXAGE=7\n# Archive status file when it gets too long\nARCHIVEMAXLINES=1000\nQUEUEURL=''\nRSYNCBASEOPTIONS='-aP'\nRSYNCRSH='ssh'\nRSYNCOPTIONS=''\n# Persistent storage directory\nLOCALROOT=\"$SCRIPTDIR/test/daemon\"\nUPLOADROOT=\"$HOME\"\nAUTOTESTGIT=\"https://chromium.googlesource.com/chromiumos/third_party/autotest\"\nTESTINGSSHKEYURL=\"https://chromium.googlesource.com/chromiumos/chromite/+/master/ssh_keys/testing_rsa\"\nMIRRORENV=\"\"\n# Maximum test run time (minutes): 24 hours\nMAXTESTRUNTIME=\"$((24*60))\"\nGSAUTOTEST=\"gs://chromeos-autotest-results\"\n# Should autotest dependencies be updated\nAUTOTESTUPDATE='y'\n\nUSAGE=\"$APPLICATION [options] -q QUEUEURL\n\nRuns a daemon that polls a CSV on the internet for tests to run, and uploads\nstatus and results to some destination via scp.\n\nOptions:\n    -e MIRRORENV   key=value pair to pass to tests to setup default mirrors.\n                   Can be specified multiple times.\n    -l LOCALROOT   Local persistent log directory\n    -q QUEUEURL    Queue URL to poll for new test requests. Must be specified.\n    -r RSYNCOPT    Special options to pass to rsync in addition to $RSYNCBASEOPTIONS\n                   Default: ${RSYNCOPTIONS:-\"(nothing)\"}\n    -s RSYNCRSH    rsh command to use for rsync.\n                   Default: $RSYNCRSH\n    -u UPLOADROOT  Base rsync-compatible URL directory to upload to. Must exist.\n                   Default: $UPLOADROOT\"\n\n# Common functions\n. \"$SCRIPTDIR/installer/functions\"\n\n# Process arguments\nwhile getopts 'e:l:q:r:s:u:x' f; do\n    case \"$f\" in\n    e) MIRRORENV=\"$OPTARG;$MIRRORENV\";;\n    l) LOCALROOT=\"$OPTARG\";;\n    q) QUEUEURL=\"$OPTARG\";;\n    r) RSYNCOPTIONS=\"$OPTARG\";;\n    s) RSYNCRSH=\"$OPTARG\";;\n    u) UPLOADROOT=\"$OPTARG\";;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\nshift \"$((OPTIND-1))\"\n\nif [ -z \"$QUEUEURL\" -o \"$#\" != 0 ]; then\n    error 2 \"$USAGE\"\nfi\n\n# No double-quotes in MIRRORENV\nif [ \"${MIRRORENV#*\\\"}\" != \"$MIRRORENV\" ]; then\n    error 2 \"$USAGE\"\nfi\n\n# Find a board name from a given host name\n# Also creates a host info file, that is used by findrelease\nfindboard() {\n    local host=\"$1\"\n\n    local hostinfo=\"$HOSTINFO/$host\"\n    local hostinfonew=\"$HOSTINFO/$host.new\"\n\n    if echo '\necho\necho \"HWID=`crossystem hwid`\"\ncat /etc/lsb-release\n' | ssh \"root@${host}.cros\" $DUTSSHOPTIONS > \"$hostinfonew\"; then\n        mv \"$hostinfonew\" \"$hostinfo\"\n    fi\n\n    if [ ! -s \"$hostinfo\" ]; then\n        echo \"Cannot fetch host info, and no cache\" 1>&2\n        return 1\n    fi\n\n    sed -n 's/^CHROMEOS_RELEASE_BOARD=//p' \"$hostinfo\"\n}\n\n# Find a release/build name from host, board and channel\nfindrelease() {\n    local host=\"$1\"\n    local channel=\"$2\"\n    local board=\"`findboard \"$host\"`\"\n    local hostinfo=\"$HOSTINFO/$host\"\n\n    local appidtype=\"RELEASE\"\n    if [ \"$channel\" = \"canary\" ]; then\n        appidtype=\"CANARY\"\n    fi\n    local appid=\"`sed -n \"s/^CHROMEOS_${appidtype}_APPID=//p\" \"$hostinfo\"`\"\n    local hwid=\"`sed -n 's/^HWID=//p' \"$hostinfo\"`\"\n\n    tee /dev/stderr<<EOF | curl -d @- https://tools.google.com/service/update2 \\\n            | tee /dev/stderr | awk '\n        BEGIN { RS=\" \"; FS=\"=\" }\n        $1 == \"ChromeOSVersion\" {\n            osver=$2; gsub(/\"/, \"\", osver)\n        }\n        $1 == \"ChromeVersion\" {\n            ver=$2; gsub(/\"/, \"\", ver); gsub(/\\..*$/, \"\", ver)\n        }\n        END {\n            if (length(ver) > 0 && length(osver) > 0)\n                print \"cros-version:'$board'-release/R\" ver \"-\" osver\n        }'\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<request protocol=\"3.0\" version=\"ChromeOSUpdateEngine-0.1.0.0\"\n                 updaterversion=\"ChromeOSUpdateEngine-0.1.0.0\">\n    <os version=\"Indy\" platform=\"Chrome OS\"></os>\n    <app appid=\"${appid}\" version=\"0.0.0\" track=\"${channel}-channel\"\n         lang=\"en-US\" board=\"${board}-signed-mp\" hardware_class=\"${hwid}\"\n         delta_okay=\"false\" fw_version=\"\" ec_version=\"\" installdate=\"2800\" >\n        <updatecheck targetversionprefix=\"\"></updatecheck>\n        <event eventtype=\"3\" eventresult=\"2\" previousversion=\"\"></event>\n    </app>\n</request>\nEOF\n}\n\nlastfullsync=0\nlastarchivesync=0\nforceupdate=\n\n# Sync status directory\n# Passing a parameter will sync that specific file only\nsyncstatus() {\n    local file=\"$1\"\n    local extraoptions=\"\"\n    if [ -z \"$file\" ]; then\n        extraoptions=\"--exclude archive --delete\"\n        local time=\"`date '+%s'`\"\n\n        # Auto-archive\n        if [ \"$((lastarchivesync+ARCHIVEINTERVAL))\" -lt \"$time\" ]; then\n            (\n                cd $STATUSROOT\n                mkdir -p \"archive\"\n                find -maxdepth 1 -type d -mtime +\"$ARCHIVEMAXAGE\" \\\n                     -regex '\\./[-0-9]*_[-0-9]*_.*' | while read -r dir; do\n                    dest=\"archive/${dir##*/}.tar.bz2\"\n                    rm -f \"$dest\"\n                    tar -caf \"$dest\" \"${dir#./}\"\n                    rm -rf \"$dir\"\n                done\n\n                # Only keep ARCHIVEMAXLINES lines of status\n                count=\"`cat status | wc -l`\"\n                if [ \"$count\" -gt \"$ARCHIVEMAXLINES\" ]; then\n                    cut=\"$((count-ARCHIVEMAXLINES/2))\"\n                    cut1=\"$((cut+1))\"\n                    head -n \"$cut\" status >> archive/status\n                    tail -n +\"$cut1\" status > status.tmp\n                    mv status.tmp status\n                    # TODO: We probably want to compress archive/status\n                fi\n            )\n            syncstatus \"archive/\"\n            forceupdate=y\n            lastarchivesync=\"$time\"\n        fi\n\n        if [ -z \"$forceupdate\" ]; then\n            if [ \"$((lastfullsync+LOGUPLOADINTERVAL))\" -gt \"$time\" ]; then\n                echo \"Skipping sync (throttling)...\" 1>&2\n                return\n            fi\n        else\n            forceupdate=\n        fi\n        lastfullsync=\"$time\"\n    fi\n    echo \"Syncing $file\" 1>&2\n    rsync $RSYNCBASEOPTIONS $RSYNCOPTIONS $extraoptions -e \"$RSYNCRSH\" \\\n            \"$STATUSROOT/$file\" \"$UPLOADROOT/$file\" >/dev/null 2>&1\n    echo \"Done\" 1>&2\n}\n\nlog() {\n    timestamp=\"`TZ= date +\"%Y-%m-%d %H:%M:%S.%N\"`\"\n    echo \"$timestamp:$@\" | tee -a \"$STATUSROOT/status\" 1>&2\n    syncstatus status\n}\n\n# Temporary files\nTMPROOT=\"`mktemp -d --tmpdir='/tmp' 'crouton-autotest.XXX'`\"\naddtrap \"rm -rf --one-file-system '$TMPROOT'\"\nLASTFILE=\"$TMPROOT/last\"\necho \"2 `date '+%s'`\" > \"$LASTFILE\"\nHOSTINFO=\"$TMPROOT/hostinfo\"\nmkdir -p \"$HOSTINFO\"\n\n# Stateful files, kept between runs\nmkdir -p \"$LOCALROOT\"\n\n# status directory: synced via rsync\nSTATUSROOT=\"$LOCALROOT/status\"\nmkdir -p \"$STATUSROOT\"\n\nlog \"crouton autotest daemon starting...\"\n\nAUTOTESTROOT=\"$HOME/trunk/src/third_party/autotest/files\"\nPATH=\"$AUTOTESTROOT/cli:$PATH\"\n\necho \"Checking if atest is present...\" 1>&2\nwhich atest\n\necho \"Checking if gsutil is installed...\" 1>&2\ngsutil version\n\necho \"Fetching testing ssh keys...\" 1>&2\nSSHKEY=\"$LOCALROOT/testing_rsa\"\nwget \"$TESTINGSSHKEYURL?format=TEXT\" -O- | base64 -d > \"$SSHKEY\"\nchmod 0600 \"$SSHKEY\"\n\n# ssh control directory\nmkdir -p \"$TMPROOT/ssh\"\n\n# ssh options for the DUTs\nDUTSSHOPTIONS=\"-o ConnectTimeout=30 -o IdentityFile=$SSHKEY \\\n-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \\\n-o ControlPath=$TMPROOT/ssh/%h \\\n-o ControlMaster=auto -o ControlPersist=10m\"\n\nsyncstatus\n\nwhile sleep \"$POLLINTERVAL\"; do\n    read -r lastline last < \"$LASTFILE\"\n    # Grab the queue, skip to the next interesting line, convert field\n    # boundaries into pipe characters, and then parse the result.\n    # Any line still containing a double-quote after parsing is ignored\n    (wget -qO- \"$QUEUEURL\" && echo) | tail -n\"+$lastline\" \\\n            | tr '\\t' '|' | {\n        while IFS='|' read -r date repo branch params run _; do\n            if [ -z \"$date\" ]; then\n                continue\n            fi\n            lastline=\"$(($lastline+1))\"\n            # Convert to UNIX time and skip if it's an old request\n            date=\"`date '+%s' --date=\"$date\"`\"\n            if [ \"$date\" -le \"$last\" ]; then\n                continue\n            fi\n            last=\"$date\"\n\n            # Validate the other fields\n            branch=\"${branch%%/*}\"\n            gituser=\"${repo%%/*}\"\n            repo=\"${repo##*/}\"\n\n            if [ \"$branch\" = \"*RELOAD\" ]; then\n                log \"Daemon: Reloading...\"\n                exit 99\n            fi\n\n            date=\"`date -u '+%Y-%m-%d_%H-%M-%S' --date=\"@$date\"`\"\n            paramsstr=\"${params:+\"_\"}`echo \"$params\" | tr ' [:punct:]' '_-'`\"\n            tname=\"${date}_${gituser}_${repo}_$branch$paramsstr\"\n            curtestroot=\"$STATUSROOT/$tname\"\n\n            # By default, try all channels and match string in \"run\"\n            channels=\"stable beta dev canary\"\n\n            # If run is a predefined string, set channels and match everything\n            if [ \"${run#SHORT}\" != \"$run\" ]; then\n                channels=\"default\"\n                run=\"-\"\n            elif [ \"${run#FULL+CANARY}\" != \"$run\" ]; then\n                channels=\"stable beta dev canary\"\n                run=\"-\"\n            elif [ \"${run#FULL}\" != \"$run\" ]; then\n                channels=\"stable beta dev\"\n                run=\"-\"\n            fi\n\n            mkdir -p \"$curtestroot\"\n\n            log \"$tname *: Dispatching (channels=$channels)\"\n\n            hostlist=\"`atest host list -w cautotest \\\n                                       -N -b pool:crouton --unlocked || true`\"\n            if [ -z \"$hostlist\" ]; then\n                log \"$tname *: Failed to retrieve host list\"\n                continue\n            fi\n\n            for host in $hostlist; do\n                if [ \"$channels\" = \"default\" ]; then\n                    # Use atest labels to select which channels to run tests on\n                    hostchannels=\"`atest host list --parse \"$host\" | \\\n                                   sed -n 's/.*|Labels=\\([^|]*\\).*/\\1/p' | \\\n                                   tr ',' '\\n' | sed -n 's/ *crouton://p'`\"\n                    if [ -z \"$hostchannels\" ]; then\n                        log \"ERROR: No default channel configured for $host.\"\n                        continue\n                    fi\n                else\n                    hostchannels=\"$channels\"\n                fi\n                for channel in $hostchannels; do\n                    # Abbreviation of host name (one letter, one number)\n                    hostshort=\"`echo \"$host\" \\\n                                    | sed -e 's/-*\\([a-z]\\)[a-z]*/\\1/g'`\"\n\n                    # Find board name\n                    board=\"`findboard \"$host\" || true`\"\n                    if [ -z \"$board\" ]; then\n                        log \"$tname $hostshort: ERROR cannot find board name!\"\n                        continue\n                    fi\n\n                    hostfull=\"$hostshort-$board-$channel\"\n\n                    # Check if hostfull matches any of the run strings\n                    match=\n                    for r in $run; do\n                        if [ \"$hostfull\" != \"${hostfull#*$r}\" ]; then\n                            match=y\n                            break\n                        fi\n                    done\n                    if [ -z \"$match\" ]; then\n                        echo \"No match for $hostfull ($run).\"\n                        continue\n                    fi\n\n                    # Find release image\n                    release=\"`findrelease \"$host\" \"$channel\" || true`\"\n                    if [ -z \"$release\" ]; then\n                        log \"$tname $hostfull: ERROR cannot find release name!\"\n                        continue\n                    fi\n\n                    curtesthostroot=\"$curtestroot/$hostfull\"\n                    if [ -d \"$curtesthostroot\" ]; then\n                        log \"$tname $hostfull: Already started\"\n                        continue\n                    fi\n\n                    mkdir -p \"$curtesthostroot\"\n\n                    # Generate control file\n                    sed -e \"s|###REPO###|$gituser/$repo|\" \\\n                        -e \"s|###BRANCH###|$branch|\" \\\n                        -e \"s|###RUNARGS###|$params|\" \\\n                        -e \"s|###ENV###|$MIRRORENV|\" \\\n                        $SCRIPTDIR/test/autotest_control.template \\\n                        > \"$curtesthostroot/control\"\n\n                    echo \"$host\" > \"$curtesthostroot/host\"\n\n                    # Run test with atest\n                    ret=\n                    (\n                        set -x\n                        atest job create -m \"$host\" -w cautotest \\\n                            -f \"$curtesthostroot/control\" \\\n                            -d \"$release\" \\\n                            -B always --max_runtime=\"$MAXTESTRUNTIME\" \\\n                            \"$tname-$hostfull\"\n                    ) > \"$curtesthostroot/atest\" 2>&1 || ret=$?\n\n                    if [ -z \"$ret\" ]; then\n                        cat \"$curtesthostroot/atest\" | tr '\\n' ' ' | \\\n                            sed -e 's/^.*(id[^0-9]*\\([0-9]*\\)).*$/\\1/' \\\n                            > \"$curtesthostroot/jobid\"\n                    else\n                        log \"$tname $hostfull: Create job failed\"\n                    fi\n                    forceupdate=y\n                done # channel\n            done # host\n            syncstatus\n        done\n        echo \"$lastline $last\" > \"$LASTFILE\"\n    }\n\n    # Check status of running tests\n    for curtestroot in \"$STATUSROOT\"/*; do\n        if [ ! -d \"$curtestroot\" ]; then\n            continue\n        fi\n        curtestupdated=\n        curtest=\"${curtestroot#$STATUSROOT/}\"\n        for curtesthostroot in \"$curtestroot\"/*; do\n            curtesthost=\"${curtesthostroot#$curtestroot/}\"\n            curtesthostresult=\"$curtesthostroot/results\"\n\n            # If jobid file exists, test is running, or results have not been\n            # fetched yet\n            if [ -f \"$curtesthostroot/jobid\" ]; then\n                jobid=\"`cat \"$curtesthostroot/jobid\"`\"\n                host=\"`cat \"$curtesthostroot/host\" || true`\"\n                newstatusfile=\"$curtesthostroot/newstatus\"\n                statusfile=\"$curtesthostroot/status\"\n                if ! atest job list --parse \"$jobid\" > \"$newstatusfile\"; then\n                    log \"$curtest $curtesthost: Cannot get status.\"\n                    continue\n                fi\n                status=\"`awk 'BEGIN {RS=\"|\";FS=\"=\"} $1~/^Status/{print $2}' \\\n                             \"$newstatusfile\"`\"\n                if ! diff -q \"$newstatusfile\" \"$statusfile\" >/dev/null 2>&1; then\n                    log \"$curtest $curtesthost: $status\"\n                    curtestupdated=y\n                    mv \"$newstatusfile\" \"$statusfile\"\n                else\n                    rm -f \"$newstatusfile\"\n                fi\n\n                # If status is Running, rsync from the host. Move the current\n                # results dir away, then use rsync --link-dest, so that partial\n                # files are used, but old files deleted\n                if [ \"$status\" = \"Running\" -a -n \"$host\" ]; then\n                    rm -rf \"$curtesthostresult.old\"\n                    mkdir -p \"$curtesthostresult\"\n                    mv -T \"$curtesthostresult\" \"$curtesthostresult.old\"\n                    mkdir -p \"$curtesthostresult\"\n                    for path in \"status.log\" \"debug/\" \\\n                        \"platform_Crouton/debug/platform_Crouton.\" \\\n                        \"platform_Crouton/results/\"; do\n                        rsync -e \"ssh $DUTSSHOPTIONS\" -aP \\\n                                         --link-dest=\"$curtesthostresult.old/\" \\\n              \"root@${host}.cros:/usr/local/autotest/results/default/${path}*\" \\\n                            \"$curtesthostresult/\" || true\n                    done\n                    rm -rf \"$curtesthostresult.old\"\n                    curtestupdated=y\n                fi\n\n                # FIXME: Any more final statuses?\n                # Actually, partial Aborted tests end up as Completed\n                # Not sure about Failed...\n                if [ \"$status\" = \"Aborted\" -o \"$status\" = \"Failed\" \\\n                                           -o \"$status\" = \"Completed\" ]; then\n                    # Get user name\n                    user=\"`awk 'BEGIN{ RS=\"|\"; FS=\"=\" }\n                                $1~/^Owner/{print $2}' \"$statusfile\"`\"\n\n                    time=\"`date '+%s'`\"\n\n                    # It may take a while for the files to be transfered, retry\n                    # for at most FETCHTIMEOUT seconds, as, sometimes, no file\n                    # ever appears (Aborted tests, for example)\n                    if ! root=\"`gsutil ls \"$GSAUTOTEST/$jobid-$user\"`\"; then\n                        echo \"Cannot fetch $jobid-$user...\" 1>&2\n                        statustimefile=\"$curtesthostroot/statustime\"\n                        if [ ! -f \"$statustimefile\" ]; then\n                            echo $time > \"$statustimefile\"\n                            continue\n                        fi\n                        statustime=\"`cat \"$statustimefile\"`\"\n                        if [ \"$((statustime+FETCHTIMEOUT))\" -gt \"$time\" ]; then\n                            continue\n                        fi\n                        status2=\"NO_DATA\"\n                    else\n                        refetchtimefile=\"$curtesthostroot/refetchtime\"\n                        # Only refetch the results after REFETCHTIME\n                        if [ -f \"$refetchtimefile\" ]; then\n                            refetchtime=\"`cat \"$refetchtimefile\"`\"\n                            if [ \"$time\" -lt \"$refetchtime\" ]; then\n                                continue\n                            fi\n                        fi\n\n                        # Ensure results are fully re-fetched\n                        rm -rf \"$curtesthostresult\" \"$curtesthostresult.old\"\n                        mkdir -p \"$curtesthostresult\"\n                        for path in \"status.log\" \"debug/\" \\\n                                \"platform_Crouton/debug/platform_Crouton.\" \\\n                                \"platform_Crouton/results/\"; do\n                            # FIXME: Can we prevent partial fetches???\n                            gsutil cp \"${root}${path}*\" \"$curtesthostresult\" \\\n                                > /dev/null 2>&1 || true\n                        done\n                        tmpdir=\"$TMPROOT/$jobid-$user\"\n                        rm -rf \"$tmpdir\"\n                        mkdir -p \"$tmpdir\"\n                        gotarchive=''\n                        if gsutil cp \"${root}platform_Crouton.tgz\" \"$tmpdir/\" > /dev/null 2>&1; then\n                            tar xf \"$tmpdir/platform_Crouton.tgz\" -C \"$tmpdir/\"\n                            mv \"$tmpdir/platform_Crouton/debug/platform_Crouton.\"* \"$curtesthostresult\" || true\n                            mv \"$tmpdir/platform_Crouton/results/\"* \"$curtesthostresult\" || true\n                            gotarchive=y\n                        fi\n                        rm -rf \"$tmpdir\"\n\n                        curtestupdated=y\n\n                        # If we got status.log and the archive, no need to refetch:\n                        # this can't race.\n                        if [ ! -f \"$curtesthostresult/status.log\" ] || [ -z \"$gotarchive\" ]; then\n                            if [ ! -f \"$refetchtimefile\" ]; then\n                                echo $((time+REFETCHTIME)) > \"$refetchtimefile\"\n                                continue\n                            fi\n                        fi\n\n                        status2=\"`awk '($1 == \"END\") && \\\n                                       ($3 == \"platform_Crouton\") \\\n                                           { print $2 }' \\\n                                       \"$curtesthostresult/status.log\" || true`\"\n                    fi\n                    log \"$curtest $curtesthost: $status ${status2:=\"UNKNOWN\"}\"\n                    sed -i -e \"s;\\$;|Status2=$status2|;\" \"$statusfile\"\n                    rm $curtesthostroot/jobid\n                fi\n            fi\n        done\n\n        # Update summary\n        (\n            cd \"$curtestroot\"\n            for dir in *; do\n                if [ -f \"$dir/status\" ]; then\n                    awk '\n                        BEGIN{ RS=\"|\"; FS=\"=\" }\n                        { data[$1] = $2 }\n                        END{ print \"'\"$dir\"'(\" data[\"Id\"] \"): \" \\\n                                   data[\"Status Counts\"] \" \" \\\n                                   data[\"Status2\"] }\n                    ' \"$dir/status\"\n                fi\n            done > \"$TMPROOT/newstatus\"\n            if ! diff -q \"$TMPROOT/newstatus\" status >/dev/null 2>&1; then\n                mv \"$TMPROOT/newstatus\" status\n                forceupdate=y\n                curtestupdated=y\n            else\n                rm -f \"$TMPROOT/newstatus\"\n            fi\n\n            if [ -n \"$curtestupdated\" ]; then\n                \"$SCRIPTDIR\"/test/genreport.sh > status.html\n            fi\n        )\n    done\n\n    # Display host status\n    atest host list --parse -w cautotest -b pool:crouton \\\n        > \"$STATUSROOT/newhoststatus\"\n    if ! diff -q \"$STATUSROOT/newhoststatus\" \\\n                 \"$STATUSROOT/hoststatus\" >/dev/null 2>&1; then\n        mv \"$STATUSROOT/newhoststatus\" \"$STATUSROOT/hoststatus\"\n        forceupdate=y\n    else\n        rm -f \"$STATUSROOT/newhoststatus\"\n    fi\n    atest host list -w cautotest -b pool:crouton \\\n        > \"$STATUSROOT/hoststatus.txt\"\n\n    syncstatus\ndone\n"
  },
  {
    "path": "test/genreport.sh",
    "content": "\n# Collect list of tests\ntests=\"`ls */results/*-test | sed -e 's|.*/\\([^\\.]*\\)\\..*|\\1|' | sort -u`\"\nmachines=\"`ls -d */status | sed -e 's|\\([^/]*\\)/.*|\\1|' | sort -u`\"\n\ncat <<EOF\n<html>\n<head>\n<style>\ntable.outer {\n    border-collapse: collapse;\n}\nth.outer,td.outer {\n    border: 1px solid black;\n    border-spacing: 0px;\n    padding: 0px;\n}\nth.outer {\n    padding: 3px\n}\ntd.outer {\n    text-align: center;\n}\ntable.inner {\n    width: 100%;\n    height: 100%;\n    border-collapse: collapse;\n}\ntd.inner {\n    padding: 3px;\n    text-align: center;\n}\n#PASSED {\n    background-color: rgb(0, 255, 0);\n}\n#FAILED {\n    background-color: rgb(255, 0, 0);\n}\n#UNKNOWN {\n    background-color: rgb(128, 128, 128);\n}\n#BLANK {\n    background-color: rgb(240, 240, 240);\n}\n</style>\n</head>\n<body>\nEOF\n\npwd=\"`pwd`\"\necho \"<h1>`basename \"$pwd\"`</h1>\"\necho \"<div>Last updated: `TZ= date` (`date +\"%H:%M:%S %Z\"`)</div>\"\n\nls \"`dirname $0`/reports/\"* 2>/dev/null | sort | while read -r report; do\n    if [ -x \"$report\" -a \"${report%\\~}\" = \"$report\" ]; then\n        echo \"Sourcing $report\" 1>&2\n        ( . \"$report\" )\n    fi\ndone\n\necho '</body></html>'\n"
  },
  {
    "path": "test/reports/00-all.sh",
    "content": "echo '<table class=\"outer\">'\necho '<tr class=\"outer\">'\necho '<th class=\"outer\"></th>'\necho '<th class=\"outer\">Status</th>'\nfor test in $tests; do\n    cnt=0\n    for mach in $machines; do\n        ncnt=\"`echo \"$mach/results/$test.\"*\"-test\" | wc -w`\"\n        if [ \"$ncnt\" -gt \"$cnt\" ]; then\n            cnt=\"$ncnt\"\n        fi\n    done\n    testshort=\"$test\"\n    if [ \"$cnt\" -lt 3 ]; then\n        testshort=\"${test%%-*}\"\n    fi\n    echo \"<th class=\\\"outer\\\" title=\\\"$test\\\">$testshort</th>\"\ndone\necho '</tr>'\n\nfor mach in $machines; do\n    echo '<tr class=\"outer\">'\n    echo \"<th class=\\\"outer\\\"><a href=\"$mach/results/\"><pre>$mach</pre></a></th>\"\n    status=\"`awk 'BEGIN {RS=\"|\";FS=\"=\"} $1~/^Status/{print $2}' \"$mach/status\"`\"\n    status2=\"`awk 'BEGIN {RS=\"|\";FS=\"=\"} $1~/^Status2/{print $2}' \"$mach/status\"`\"\n    statusid=\"UNKNOWN\"\n    if [ -n \"$status2\" ]; then\n        if [ \"$status2\" = \"GOOD\" ]; then\n            statusid=\"PASSED\"\n        else\n            statusid=\"FAILED\"\n        fi\n    else\n        if [ \"$status\" = \"Running\" -o \"$status\" = \"Completed\" ]; then\n            statusid=\"UNKNOWN\"\n        elif [ \"$status\" = \"Queued\" ]; then\n            statusid=\"BLANK\"\n        else\n            statusid=\"FAILED\"\n        fi\n    fi\n    echo \"<th class=\\\"outer\\\" id=\\\"$statusid\\\">$status</td>\"\n    for test in $tests; do\n        echo '<td class=\"outer\">'\n        echo '<table class=\"inner\">'\n        echo '<tr class=\"inner\">'\n        for fulltest in \"$mach/results/$test.\"*\"-test\"; do\n            if [ ! -f \"$fulltest\" ]; then\n                echo \"<td class=\\\"inner\\\" id=\\\"BLANK\\\">&nbsp;</td>\"\n                break\n            fi\n            fulllog=\"${fulltest%-test}\"\n            short=\"${fulllog#$mach/results/$test.}\"\n            short=\"${short%.0}\"\n            xshort=\"`echo $short | sed -e 's/^\\([a-z][a-z]\\)[a-z]*/\\1/'`\"\n            status=\"`tail -n 1 \"$fulltest\" | sed -n 's/.*TEST \\([A-Z]*\\):.*/\\1/p'`\"\n            echo \"<td class=\\\"inner\\\" id=\\\"${status:-UNKNOWN}\\\"><a title=\\\"$short\\\" href=\\\"$fulllog\\\">$xshort</a></td>\"\n        done\n        echo '</tr>'\n        echo '</table>'\n        echo '</td>'\n    done\n    echo '</tr>'\ndone\n\necho '</table>'\n"
  },
  {
    "path": "test/reports/35-xorg.sh",
    "content": "## Add output from 35-xorg tests\ntest=\"35-xorg\"\n\nxorgreleases=\"`for mach in $machines; do\n    for fulltest in \"$mach/results/$test.\"*\"-test\"; do\n        if [ ! -f \"$fulltest\" ]; then\n            continue\n        fi\n        fulllog=\"${fulltest%.*-test}\"\n        short=\"${fulllog#$mach/results/$test.}\"\n        echo $short\n    done\ndone | sort -u`\"\n\nif [ -z \"$xorgreleases\" ]; then\n    exit 0\nfi\n\necho \"<h2>$test results</h2>\"\necho '<table class=\"outer\">'\necho '<tr class=\"outer\">'\necho '<th class=\"outer\"></th>'\nfor release in $xorgreleases; do\n    cnt=0\n    echo \"<th class=\"outer\">$release</th>\"\ndone\necho '</tr>'\n\nfor mach in $machines; do\n    onetest=\"`ls \"$mach/results/$test.\"*\"-test\" 2>/dev/null | tail -n 1`\"\n    if [ ! -f \"$onetest\" ]; then\n        continue\n    fi\n\n    echo '<tr class=\"outer\">'\n    echo \"<th class=\"outer\"><pre>$mach</pre></th>\"\n    for release in $xorgreleases; do\n        # Check out the latest test only\n        besttest=\"`ls \"$mach/results/$test.$release.\"*\"-test\" 2>/dev/null | sort | tail -n 1`\"\n        if [ ! -f \"$besttest\" ]; then\n            echo '<td class=\"outer\">&nbsp;</td>'\n            continue\n        fi\n\n#if [ -z \"$driinfo\" ] || [ -z \"$render\" ]; then\n#            color=\"pink\"\n#         elif ! echo \"$driinfo\" | grep -q 'i9.5' || ! echo \"$render\" | grep -q 'Intel'; then\n#            color=\"yellow\"\n#         fi\n\n        awk '\n/ ====\\/GLX info/ { g=3 }\n(g == 2) && /OpenGL renderer string: / {\n    renderer=$0\n    gsub(/.*OpenGL renderer string: /, \"\", renderer)\n}\n(g == 1) && /==glxinfo/ { g=2 }\n(g == 1) {\n    key=$0; sub(/:.*$/, \"\", key); gsub(/^.* /, \"\", key)\n    val=$0; sub(/^[^:]*:/, \"\", val)\n    data[key] = val\n}\n/ ====GLX info/ { g=1 }\nEND {\n    split(data[\"uname\"], kernel, \" \")\n    sub(/^.*: /, \"\", data[\"xdriinfo\"])\n    statusid=\"PASSED\"\n    if (g != 3) {\n        statusid=\"UNKNOWN\"\n    } else if (data[\"xdriinfo\"] !~ /i9.5/ || renderer !~ /Intel/) {\n        statusid=\"FAILED\"\n    }\n    print \"<td class=\\\"outer\\\" id=\\\"\" statusid \"\\\"><pre>\"\n    print \"id: \" data[\"vendor\"] \"/\" data[\"device\"]\n    print \"kernel: \" kernel[3]\n    print \"driinfo: \" data[\"xdriinfo\"]\n    print \"renderer: \" renderer\n    print \"</pre></td>\"\n}\n' \"$besttest\"\n    done\n    echo '</tr>'\ndone\n\necho '</table>'\n\n"
  },
  {
    "path": "test/reports/w0-common.sh",
    "content": "\ntests=\"`ls */results/w*-test | sed -e 's|.*/||;s|\\..*||' | sort | uniq`\"\n\nif [ -z \"$tests\" ]; then\n    exit 0\nfi\n\nfor test in $tests; do\nxorgreleases=\"`for mach in $machines; do\n    for fulltest in \"$mach/results/$test.\"*\"-test\"; do\n        if [ ! -f \"$fulltest\" ]; then\n            continue\n        fi\n        fulllog=\"${fulltest%.*-test}\"\n        short=\"${fulllog#$mach/results/$test.}\"\n        echo $short\n    done\ndone | sort -u`\"\n\nif [ -z \"$xorgreleases\" ]; then\n    exit 0\nfi\n\necho \"<h2>$test</h2>\"\necho '<table class=\"outer\">'\necho '<tr class=\"outer\">'\necho '<th class=\"outer\"></th>'\nfor release in $xorgreleases; do\n    cnt=0\n    echo \"<th class=\"outer\">$release</th>\"\ndone\necho '</tr>'\n\nfor mach in $machines; do\n    onetest=\"`ls \"$mach/results/$test.\"*\"-test\" 2>/dev/null | tail -n 1`\"\n    if [ ! -f \"$onetest\" ]; then\n        continue\n    fi\n\n    echo '<tr class=\"outer\">'\n    echo \"<th class=\"outer\"><pre>$mach</pre></th>\"\n    for release in $xorgreleases; do\n        # Check out the latest test only\n        besttest=\"`ls \"$mach/results/$test.$release.\"*\"-test\" 2>/dev/null | sort | tail -n 1`\"\n        if [ ! -f \"$besttest\" ]; then\n            echo '<td class=\"outer\">&nbsp;</td>'\n            continue\n        fi\n\n        fulltest=\"${besttest%-test}\"\n        image=\"${besttest%-test}-snapshot.jpg\"\n\n        status=\"`tail -n 1 \"$besttest\" | sed -n 's/.*TEST \\([A-Z]*\\):.*/\\1/p'`\"\n        tdid=\"${status:-UNKNOWN}\"\n        if [ -f \"$image\" ]; then\n            thumbdir=\"$mach/results.thumb\"\n            thumb=\"${image#$mach/results/}\"\n            thumb=\"$thumbdir/$thumb\"\n\n            if [ ! -f \"$thumb\" -o \"$image\" -nt \"$thumb\" ]; then\n                mkdir -p \"$thumbdir\"\n                convert -resize 100x100 -quality 90 \"$image\" \"$thumb\"\n            fi\n        else\n            if grep -q \"unsupported combination\" \"$besttest\"; then\n                tdid=\"BLANK\"\n            fi\n        fi\n\n        echo \"<td class=\\\"outer\\\" id=\\\"$tdid\\\">\"\n        if [ -f \"$image\" ]; then\n            echo -n \"<a href=\\\"$image\\\"/>\"\n            echo -n \"<img src=\\\"$thumb\\\"/ width=100/>\"\n            echo \"</a><br/>\"\n        fi\n        echo \"<a href=\\\"$fulltest\\\"/>log</a>\"\n        echo \"</td>\"\n    done\n    echo '</tr>'\ndone\n\necho '</table>'\n\ndone # test in $tests\n"
  },
  {
    "path": "test/run.sh",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Provides common functions and variables, then runs all of the tests in tests/\n\n# Tests numbering is categorical in 10's by the following guide:\n#   0*: meta-tests, e.g. tester\n#   1*: core tests, e.g. basic, background, upgrade\n#   3*: small-target/tech tests, e.g. cli-extra, audio\n#   5*: DE tests, e.g. xfce, xbmc\n#   9*: misc application tests, e.g. chrome\n# Alphabetic tests are long, and not run by default:\n#   w*: Start all DE/wm, and take snapshots\n#   x*: Install test all targets that do not have tests\n# Numbering within a category is arbitrary and can have overlaps.\n# Tests are always run in alphanumeric order unless specified by parameters.\n\nset -e\n\nAPPLICATION=\"${0##*/}\"\nSCRIPTDIR=\"`readlink -f -- \"\\`dirname \"$0\"\\`/..\"`\"\n# List of all supported (non-*'d) releases\nSUPPORTED_RELEASES=\"`awk -F'|' '\n    ($1 ~ /[a-z]$/ && $2 !~ /(^|,)notest($|,)/) || $2 ~ /(^|,)test($|,)/ \\\n        { sub(/[^a-z]*$/, \"\", $1); printf $1 \" \" }' \\\n    \"$SCRIPTDIR/installer/\"*\"/releases\"`\"\nSUPPORTED_RELEASES=\"${SUPPORTED_RELEASES%\" \"}\"\nSUPPORTED_RELEASES_SET=''\n# System info\nSYSTEM=\"`awk -F= '/_RELEASE_DESCRIPTION=/ {print $2}' /etc/lsb-release`\"\nTESTDIR=\"$SCRIPTDIR/test/run\"\nTESTNAME=\"`sh -e \"$SCRIPTDIR/build/genversion.sh\" test`\"\nTESTDIR=\"$TESTDIR/$TESTNAME\"\n# PREFIX intentionally includes a space. Run in /usr/local to avoid encryption\nPREFIXROOT=\"/usr/local/$TESTNAME prefix\"\nRELEASE=''\nMAXTRIES=3\n\n# Default: run 3 jobs in parallel\nJOBS=3\n\nUSAGE=\"$APPLICATION [options] [test [...]]\n\nRuns tests of the crouton infrastructure. Omitting specific tests will run all\nof the numbered tests in sequence (unnumbered tests are skipped).\nAlternatively, you can specify one or more test name prefixes that are matched\nagainst the test names and run.\n\nTests are run out of a unique subdirectory in /usr/local, and the results are\nstored in a unique subdirectory of $SCRIPTDIR/test/run\n\nOptions:\n    -j JOBS      Number of tests to run in parallel. Default: $JOBS\n    -l LOGDIR    Put test logs in the specified directory.\n                 Default: $TESTDIR\n    -r RELEASE   Specify a release to use whenever it shouldn't matter.\n                 Default is a random supported release, considering -R.\n    -R RELEASES  Limit the 'all supported releases' testing to a comma-separated\n                 list of releases. Default: all supported releases are tested.\n    -T MAXTRIES  Number of times to repeat a failed test (default: $MAXTRIES)\"\n\n# Common functions\n. \"$SCRIPTDIR/installer/functions\"\n\n# Process arguments\nwhile getopts 'j:l:r:R:T:' f; do\n    case \"$f\" in\n    j) JOBS=\"$OPTARG\";;\n    l) TESTDIR=\"${OPTARG%/}\";;\n    r) RELEASE=\"$OPTARG\";;\n    R) SUPPORTED_RELEASES=\"`echo \"$OPTARG\" | tr ',' ' '`\";\n       SUPPORTED_RELEASES_SET='y';;\n    T) MAXTRIES=\"$OPTARG\";;\n    \\?) error 2 \"$USAGE\";;\n    esac\ndone\nshift \"$((OPTIND-1))\"\n\n# We need to run as root\nif [ \"$USER\" != root -a \"$UID\" != 0 ]; then\n    error 2 \"${0##*/} must be run as root.\"\nfi\n\n# Choose a random release to test when the release (shouldn't) matter\n# This is a list: when a test fail on a given release, the next one is picked\nif [ -z \"$RELEASE\" ]; then\n    RELEASE=\"`echo \"$SUPPORTED_RELEASES\" | tr ' ' \"\\n\" | sort -R | tr \"\\n\" ' '`\"\nfi\n\necho \"Running tests in $PREFIXROOT\" 1>&2\necho \"Logging to $TESTDIR\" 1>&2\necho \"System: $SYSTEM\" 1>&2\necho \"Supported releases: $SUPPORTED_RELEASES\" 1>&2\necho \"Default release for this run: ${RELEASE%% *}\" 1>&2\n\n# Logs all output to the specified file with the date and time prefixed.\n# File is always appended. Use \"log\" to output a line with a [t] prefix.\n# $1: log file or directory\n# $2: script to source, run, and log\n# $3: release to run the test on\n# $4: number of attempts so far\nlogto() {\n    local file=\"$1\" line='{print strftime(\"%F %T: \") $0}'\n    shift\n    if [ -d \"$file\" ]; then\n        file=\"$file/log\"\n    fi\n    local testlog=\"$file-test\"\n    date \"+%F %T: BEGIN TEST ${1##*/}.$2.$3\" | tee -a \"$file\" \"$testlog\" 1>&2\n    local start=\"`date '+%s'`\"\n    local retpreamble=\"${1##*/}.$2.$3 finished with exit code\"\n    local AWK='mawk -W interactive'\n    # srand() uses system time as seed but returns previous seed. Call it twice.\n    ((((ret=0; TRAP=''\n        (\n            PREFIX=\"`mktemp -d --tmpdir=\"$PREFIXROOT\" \"$tname.XXX\"`\"\n            # Remount noexec/etc to make the environment as harsh as possible\n            mount --bind \"$PREFIX\" \"$PREFIX\"\n            mount -i -o remount,nosuid,nodev,noexec \"$PREFIX\"\n\n            # Get subshell pid\n            pid=\"`sh -c 'echo $PPID'`\"\n\n            # Clean up on exit\n            settrap \"\n                set -x\n                echo Running trap...\n                if [ -d '$PREFIX/chroots' ]; then\n                    sh -e '$SCRIPTDIR/host-bin/unmount-chroot' \\\n                        -a -f -y -c '$PREFIX/chroots'\n                fi\n                # Kill any leftover subprocess\n                pkill -9 -P '$pid'\n                umount -l '$PREFIX'\n                rm -rf --one-file-system '$PREFIX'\n            \"\n            release=\"$2\" . \"$1\"\n        ) </dev/null 3>&- || ret=$?\n        sleep 1\n        if [ \"$ret\" = 0 ]; then\n            log \"TEST PASSED: $retpreamble $ret\"\n        else\n            log \"TEST FAILED: $retpreamble $ret\"\n        fi\n        )      | $AWK '{srand(); print (srand()-'\"$start\"') \" [i] \" $0}' 1>&3\n        ) 2>&1 | $AWK '{srand(); print (srand()-'\"$start\"') \" [e] \" $0}' 1>&3\n        ) 9>&1 | $AWK '{srand(); print (srand()-'\"$start\"') \" [t] \" $0}' \\\n                    | tee -a \"$testlog\" 1>&3\n    ) 3>> \"$file\"\n    # Output the return and relay success\n    tail -n1 \"$testlog\" | tee /dev/stderr | grep -q \"$retpreamble 0\\$\"\n}\n\n# Outputs a line to the log with a [t] prefix.\n# Either specify the text as a parameter, or stdin if no parameters are given.\nlog() {\n    if [ \"$#\" != 0 ]; then\n        echo \"$*\" 1>&9\n    else\n        cat 1>&9\n    fi\n}\n\n# Tests and outputs success or failure. Parameters are the same as \"test\".\n# Returns 1 on failure\ntest() {\n    if [ \"$@\" ]; then\n        log \"PASS: [ $* ]\"\n        return 0\n    else\n        log \"FAIL: [ $* ]\"\n        return 1\n    fi\n}\n\n# Returns a temporary file in the prefix, so you don't have to clean it up\n# $1 (optional): specify a prefix\ntmpfile() {\n    mktemp --tmpdir=\"$PREFIX\" \"${1:-tmp}.XXXXXX\"\n}\n\n# Launches the installer with the specified parameters; auto-includes -p\n# If -T is the first parameter, passes stdin into /prepare.sh\n# -T auto-depends on core if -U or -u is not specified.\ncrouton() {\n    local ret='0' tfile=''\n    if [ \"$1\" = '-T' ]; then\n        shift\n        local param='' requires='core'\n        for param in \"$@\"; do\n            if [ \"$param\" = '-U' -o \"$param\" = '-u' ]; then\n                requires=''\n                break\n            fi\n        done\n        tfile=\"`mktemp --tmpdir=\"$PREFIX\" target.XXXXXX`\"\n        {\n            echo \"REQUIRES='$requires'\"'\n. \"${TARGETSDIR:=\"$PWD\"}/common\"\n### Append to prepare.sh:\nset -x'\n            cat\n            echo '\nset +x'\n        } > \"$tfile\"\n    fi\n    log \"LAUNCHING: crouton${tfile:+\" -T \"}$tfile $*\"\n    if [ -n \"$tfile\" ]; then\n        log \"BEGIN $tfile CONTENTS\"\n        cat \"$tfile\" | log\n        log \"END $tfile CONTENTS\"\n        sh -e \"$SCRIPTDIR/installer/main.sh\" -T \"$tfile\" -p \"$PREFIX\" \"$@\" \\\n            || ret=\"$?\"\n    else\n        sh -e \"$SCRIPTDIR/installer/main.sh\" -p \"$PREFIX\" \"$@\" || ret=\"$?\"\n    fi\n    if [ -n \"$tfile\" ]; then\n        rm -f \"$tfile\"\n    fi\n    if [ \"$ret\" = 0 ]; then\n        log \"PASS: crouton $*\"\n    else\n        log \"FAIL with code $ret: crouton $*\"\n    fi\n    return \"$ret\"\n}\n\n# Downloads a bootstrap if not done already and returns the tarball.\n# Safe to run in parallel.\n# $1: the release to bootstrap\n# returns the path on stdout.\nbootstrap() {\n    local file=\"$PREFIXROOT/$1-bootstrap.tar.gz\"\n    echo \"$file\"\n\n    # Use flock so that bootstrap can be called in parallel\n    if ! flock -n 4; then\n        log \"Waiting for bootstrap for $1 to complete...\"\n        flock 4\n    elif [ ! -s \"$file\" ]; then\n        crouton -r \"$1\" -f \"$file\" -d 1>&2\n    fi 4>>\"$file\"\n\n    if [ ! -s \"$file\" ]; then\n        log \"FAIL due to incomplete bootstrap for $1\"\n        return 1\n    fi\n}\n\n# Downloads and installs a basic chroot with the specified targets, then backs\n# it up for quicker test starts. Safe to run in parallel; takes advantage of\n# bootstraps as well.\n# $1: the release to install\n# $2: comma-separated list of targets. defaults to 'core'\n# $3: name of the chroot to extract to; default is the release name\nsnapshot() {\n    local targets=\"${2:-core}\"\n    local file=\"$PREFIXROOT/$1-$targets-snapshot.tar.gz\"\n    local name=\"${3:-\"$1\"}\"\n\n    # Use flock so that snapshot can be called in parallel\n    if ! flock -n 4; then\n        log \"Waiting for snapshot for $1-$targets to complete...\"\n        flock 4\n    elif [ ! -s \"$file\" ]; then\n        crouton -f \"`bootstrap \"$1\"`\" -t \"$targets\" -n \"$name\" 1>&2\n        host edit-chroot -y -b -f \"$file\" \"$name\"\n        return 0\n    fi 4>>\"$file\"\n\n    # Restore the snapshot into place\n    crouton -f \"$file\" -n \"$name\"\n}\n\n# Runs a host command with the specified parameters\n# $1 command to run (enter-chroot, etc)\n# $2+ parameters\nhost() {\n    local cmd=\"$1\" ret='0'\n    shift\n    log \"LAUNCHING: $cmd $*\"\n    sh -e \"$PREFIX/bin/$cmd\" \"$@\" || ret=\"$?\"\n    if [ \"$ret\" = 0 ]; then\n        log \"PASS: $cmd $*\"\n    else\n        log \"FAIL with code $ret: $cmd $*\"\n    fi\n    return \"$ret\"\n}\n\n# Returns success if command exits with code X within Y seconds\n# If the command takes longer than Y seconds, kills it with SIGTERM\n# $1 exit code to expect\n# $2 number of seconds within which the command should exit\n# $3+ command to run\nexitswithin() {\n    local code=\"$1\" seconds=\"$2\" ret=0\n    shift 2\n    log \"Expecting '$*' to exit with code $code within $seconds seconds\"\n    \"$@\" &\n    local pid=\"$!\"\n    (sleep \"$seconds\" && exec kill -TERM \"$pid\") &\n    local sleepid=\"$!\"\n    wait \"$pid\" || ret=\"$?\"\n    sleep .1\n    if kill \"$sleepid\" 2>/dev/null; then\n        if [ \"$ret\" = \"$code\" ]; then\n            log \"PASS: '$*' exited with code $ret within $seconds seconds\"\n        else\n            log \"FAIL: '$*' exited with code $ret instead of $code\"\n            return 1\n        fi\n    else\n        log \"FAIL: '$*' took more than $seconds seconds to exit\"\n        return 2\n    fi\n    return 0\n}\n\n# Returns success if command runs longer than X seconds; exit code is ignored\n# If the command survives longer than X seconds, kills it with SIGINT\n# $1 number of seconds the command should survive\n# $2+ command to run\nrunslongerthan() {\n    local seconds=\"$1\" ret=0\n    shift\n    log \"Expecting '$*' to survive longer than $seconds seconds\"\n    \"$@\" &\n    local pid=\"$!\"\n    (sleep \"$seconds\" && exec kill -INT \"$pid\") &\n    local sleepid=\"$!\"\n    wait \"$pid\" || ret=\"$?\"\n    sleep .1\n    if kill \"$sleepid\" 2>/dev/null; then\n        log \"FAIL: '$*' did not survive at least $seconds seconds (returned $ret)\"\n        return 2\n    else\n        log \"PASS: '$*' survived for $seconds seconds (returned $ret)\"\n    fi\n    return 0\n}\n\n# Expects a successful return code from the provided command.\npasses() {\n    log \"Expecting '$*' to succeed\"\n    local ret=0\n    \"$@\" || ret=$?\n    if [ \"$ret\" = 0 ]; then\n        log \"PASS: '$*' succeeded\"\n    else\n        log \"FAIL: '$*' returned $ret\"\n        return \"$ret\"\n    fi\n    return 0\n}\n\n# Expects a failure return code from the provided command.\nfails() {\n    log \"Expecting '$*' to fail\"\n    local ret=0\n    \"$@\" || ret=$?\n    if [ \"$ret\" != 0 ]; then\n        log \"PASS: '$*' returned $ret\"\n    else\n        log \"FAIL: '$*' succeeded\"\n        return 1\n    fi\n    return 0\n}\n\n# Sources a function from a script for unit testing.\n# Note that the function may need global variables defined when run.\n# Usage:\n#   from scriptfile import functionA[[,] functionB]*\n# scriptfile should be tarball-relative, i.e., host-bin/mount-chroot\nfrom() {\n    local scriptpath=\"$SCRIPTDIR/$1\" scriptfile=\"$1\" name script\n    if [ \"$2\" != 'import' -o -z \"$3\" ]; then\n        echo \"    $*\nSyntaxError: invalid syntax\" 1>&2\n        return 2\n    fi\n    if [ ! -f \"$scriptpath\" ]; then\n        echo \"ImportError: No module named $scriptfile\" 1>&2\n        return 2\n    fi\n    shift 2\n    for name in \"$@\"; do\n        name=\"${name%,}\"\n        script=\"`awk '\n            /^'\"$name\"'[(][)] {$/ {x=1}\n            x;\n            x && /^}$/ {exit}\n        ' \"$scriptpath\"`\"\n        if [ -z \"$script\" ]; then\n            echo \"ImportError: cannot import name $name\" 1>&2\n            return 2\n        fi\n        log \"Importing $name from $scriptfile\"\n        eval \"$script\"\n    done\n    return 0\n}\n\n# Ensures only one test can play with graphics at one time\n# Run it without parameters, in a subshell. The lock will be released when\n# the subshell exits\nvtlock() {\n    local vtlockfile='/var/lock/croutonvt'\n    exec 4>>\"$vtlockfile\"\n    if ! flock -n 4; then\n        log 'Waiting for VT lock...'\n        flock 4\n    fi\n}\n\n# Runs the provided command under vtlock\nvtlockrun() {\n    (\n        vtlock\n        \"$@\" || return $?\n    )\n}\n\n# Default responses to questions\nexport CROUTON_USERNAME='test'\nexport CROUTON_PASSPHRASE='hunter2'\nexport CROUTON_NEW_PASSPHRASE=\"$CROUTON_PASSPHRASE\"\nexport CROUTON_EDIT_RESPONSE='y'\nexport CROUTON_MOUNT_RESPONSE='y'\nexport CROUTON_UNMOUNT_RESPONSE='y'\n# Test machines lack entropy: Use /dev/urandom instead of /dev/random\nexport CROUTON_WEAK_RANDOM='y'\n# HACK: restart debugd when running tests to avoid namespace issues.\n# This will go away when we start using mount namespaces.\nexport CROUTON_UNMOUNT_RESTART_DEBUGD='y'\n\n# Prevent powerd from sleeping the system\nsh -e \"$SCRIPTDIR/chroot-bin/croutonpowerd\" -i &\ncroutonpowerd=\"$!\"\n\n# Run all the tests\nmkdir -p \"$TESTDIR\" \"$PREFIXROOT\"\naddtrap \"echo 'Cleaning up...' 1>&2\n    pkill debootstrap 2>/dev/null\n    kill \\$jobpids 2>/dev/null\n    for pid in \\$jobpids; do\n        wait \\$pid 2>/dev/null\n    done\n    for m in '$PREFIXROOT/'*; do\n        if [ -d \\\"\\$m/chroots\\\" ]; then\n            sh -e '$SCRIPTDIR/host-bin/unmount-chroot' -a -y -c \\\"\\$m/chroots\\\"\n        fi\n        if mountpoint -q \\\"\\$m\\\"; then\n            umount -l \\\"\\$m\\\" 2>/dev/null\n        fi\n    done\n    rm -rf --one-file-system '$PREFIXROOT'\n    kill '$croutonpowerd' 2>/dev/null\"\n\n# If no arguments were passed, match all numbered tests\nif [ \"$#\" = 0 ]; then\n    set -- 0 1 2 3 4 5 6 7 8 9\nfi\n\njobpids=''\nfail=0\n\n# Waits for there to be fewer than $1 jobs running. Reads and updates $jobpids.\n# If $1 is omitted, uses $JOBS\n# If 0 is specified, assumes 1\nwaitjobs() {\n    local j=0\n    while [ -n \"$jobpids\" -a \"$j\" -le 0 ]; do\n        j=\"${1:-\"$JOBS\"}\"\n        if [ \"$j\" -lt 1 ]; then\n            j=1\n        fi\n        local newjobpids='' pid\n        for pid in $jobpids; do\n            if kill -0 \"$pid\" 2>/dev/null; then\n                j=\"$((j-1))\"\n                newjobpids=\"$newjobpids $pid\"\n            else\n                # Grab the return code\n                if ! wait \"$pid\"; then\n                    fail=\"$((fail+1))\"\n                fi\n            fi\n        done\n        jobpids=\"${newjobpids# }\"\n        sleep 1\n    done\n}\n\n# Queue format: \"test file|release|number of tries\n# Make sure QUEUELOCK is held when modifying QUEUEFILE\nQUEUEFILE=\"`tmpfile queue`\"\nQUEUEFILETMP=\"`tmpfile queuetmp`\"\nQUEUELOCK=\"`tmpfile queuelock`\"\n\n# Add all tests matching the supplied prefixes to the queue (random order)\ntname=''\nfor p in \"$@\"; do\n    for t in \"$SCRIPTDIR/test/tests/$p\"*; do\n        if [ ! -s \"$t\" -o \"${t%~}\" != \"$t\" ]; then\n            continue\n        fi\n\n        # When $release is blank, the tests output a list of supported releases,\n        # that is intersected with $SUPPORTED_RELEASES. Also, \"all\" indicates\n        # that all releases are supported, and \"default\" indicates that only a\n        # default release should be tested\n        testrel=\"\"\n        for rel in `release=\"\" . \"$t\"`; do\n            for sup in $SUPPORTED_RELEASES; do\n                if [ \"$rel\" = \"all\" -o \"$rel\" = \"$sup\" ]; then\n                    echo \"$t|$sup|0\"\n                fi\n            done\n\n            # default is translated later\n            if [ \"$rel\" = \"default\" ]; then\n                echo \"$t|default|0\"\n            fi\n        done\n    done\ndone | sort -u | sort -R > \"$QUEUEFILE\"\n\nif [ ! -s \"$QUEUEFILE\" ]; then\n    echo \"No tests found matching $*\" 1>&2\n    exit 2\nfi\n\n# Start jobs from the queue\nwhile true; do\n    # Wait for a free slot (max $JOBS at a time)\n    waitjobs\n    if [ ! -s \"$QUEUEFILE\" ]; then\n        if [ -z \"$jobpids\" ]; then\n            break\n        fi\n        # Queue empty, but jobs are still running, check again in a while.\n        sleep 10\n        continue\n    fi\n    job=\"`(\n        flock 3\n        head -n 1 \"$QUEUEFILE\"\n        tail -n +2 \"$QUEUEFILE\" > \"$QUEUEFILETMP\"\n        mv \"$QUEUEFILETMP\" \"$QUEUEFILE\"\n    ) 3>\"$QUEUELOCK\"`\"\n    if [ -z \"$job\" ]; then\n        continue\n    fi\n\n    # Split job line\n    t=\"${job%%|*}\"\n    job=\"${job#$t|}\"\n    jobrel=\"${job%%|*}\"\n    job=\"${job#$jobrel|}\"\n    try=\"${job%%|*}\"\n\n    # jobrel is the release indicated in the job line (can be default)\n    # rel is an actual release\n    if [ \"$jobrel\" = \"default\" ]; then\n        # Select element $try in $RELEASE (wrap around)\n        rel=\"`echo \"$RELEASE\" | awk '{x=('\"$try\"'%NF)+1; print $x; exit}'`\"\n    else\n        rel=\"$jobrel\"\n    fi\n\n    tname=\"${t##*/}.$rel.$try\"\n    # Run the test\n    (\n        if ! logto \"$TESTDIR/$tname\" \"$t\" \"$rel\" \"$try\"; then\n            if [ \"$((try+1))\" -lt \"$MAXTRIES\" ]; then\n                # Test failed, try again...\n                (\n                    flock 3\n                    echo \"$t|$jobrel|$((try+1))\" >> \"$QUEUEFILE\"\n                ) 3>\"$QUEUELOCK\"\n            else\n                exit 1\n            fi\n        fi\n    ) &\n    jobpids=\"$jobpids${jobpids:+\" \"}$!\"\ndone\n\n# Clean up /var/run/crouton\nrm -rf --one-file-system /var/run/crouton || true\n\nif [ \"$fail\" -gt 0 ]; then\n   echo \"$fail test(s) failed.\" 1>&2\n   exit 1\nelse\n   echo \"All tests passed.\" 1>&2\nfi\n"
  },
  {
    "path": "test/tests/00-tester",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Tests some of the more complex testing functions, and records the system info\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\n# Save the basic system info to the log\nlog \"System info\"\ncat /etc/lsb-release | log\nlog \"Mirror info\"\nenv | grep CROUTON_MIRROR | log\n\n# Make sure test(), exitswithin() and runslongerthan(), passes(), fails()\n# work as expected\ntest 1 -eq 1\npasses true\nfails false\nexitswithin 0 1 true\nexitswithin 1 1 false\nrunslongerthan 1 sleep 2\n\n# Test \"from\" function importer by faking it out\n: '\nfromtester() {\n    return 0\n}\n'\npasses from 'test/tests/00-tester' import fromtester\npasses fromtester\n\nlog 'BEGIN INTENTIONALLY FAILING TESTS'\n! test 1 -eq 0\n! passes false\n! fails true\n! exitswithin 0 1 false\n! exitswithin 1 1 true\n! exitswithin 0 1 sleep 2\n! runslongerthan 1 false\n! runslongerthan 1 true\nfails from sys import posix\nfails from 'test/tests/00-tester' import python\nlog 'END INTENTIONALLY FAILING TESTS'\n"
  },
  {
    "path": "test/tests/10-basic",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# All supported releases should be able to create a core chroot and enter it.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ncrouton -r \"$release\" -t core\nhost enter-chroot -n \"$release\" true\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/11-32-on-64",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# All supported releases should be able to create an i686 chroot on x86-64\n\nif [ -z \"$release\" ]; then\n    if [ \"`uname -m`\" = \"x86_64\" ]; then\n        echo \"all\"\n    fi\n    exit 0\nfi\n\ncrouton -a x86 -r \"$release\" -t core\nhost enter-chroot -n \"$release\" true\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/12-edit-chroot",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Ensure all parameters of edit-chroot work properly (except encryption,\n# tested in 17-encryption).\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\nBACKUPDIR=\"$PREFIX/my backups\"\nmkdir -p \"$BACKUPDIR\"\n\n# Release-independent.\nsnapshot \"$release\" core\n\nhost enter-chroot -n \"$release\" true\n\n# Tests with single parameter\n\n# List all chroots\nhost edit-chroot -a | tee /dev/stderr | passes grep \"$release\"\n\n# Check that the list all chroots option fails with anything else entered on the command line\nfails host edit-chroot -a $release\n\n# List all chroots with details\nhost edit-chroot -al | tee /dev/stderr | passes grep \"^release: $release\"\n\n# List one chroot with details\nhost edit-chroot -l $release | tee /dev/stderr | passes grep \"^release: $release\"\n\n# Cannot list a non-existing chroot's 'croutonversion'\nfails host edit-chroot -l x$release\n\n# Check that the list all chroots with details option fails with anything else entered on the command line\nfails host edit-chroot -al $release\n\n# Add cli-extra target, so we can check that startcli gets restored\ncrouton -u -n \"$release\" -t cli-extra\ntest -f \"$PREFIX/bin/startcli\"\n\n# Check that we can restore host-bin files.\nmd5sum \"$PREFIX/bin\"/* > \"$BACKUPDIR/csum\"\nrm \"$PREFIX/bin\"/*\nfails md5sum -c \"$BACKUPDIR/csum\"\nfails host enter-chroot -n \"$release\" true\n# Create a dummy directory to check that crouton -b ignores invalid chroots\nmkdir \"$PREFIX/chroots/dummy\"\n# Restore binaries\ncrouton -b\nmd5sum -c \"$BACKUPDIR/csum\"\ntest -f \"$PREFIX/bin/startcli\"\nrm \"$BACKUPDIR/csum\"\nhost enter-chroot -n \"$release\" true\n# Verify croutonversion outputs correct information\nhost edit-chroot -l $release | tee /dev/stderr | passes grep \"^release: $release\"\n\n# Cannot backup a non-existing chroot\nfails host edit-chroot -y -f \"$BACKUPDIR\" -b \"$release.x\"\nfails ls \"$BACKUPDIR\"/*\n\n# BACKUPDIR contains a space: these must fail as well\nfails host edit-chroot -y -b -f $BACKUPDIR \"$release\"\nfails host edit-chroot -y -f $BACKUPDIR -b \"$release\"\nfails ls \"$BACKUPDIR\"/*\n\n# Backup a chroot\nfails ls \"$BACKUPDIR\"/*\nhost edit-chroot -y -f \"$BACKUPDIR/$release.tar\" -b \"$release\"\n\n# Move an existing chroot\nhost edit-chroot -y \"$release\" -m \"$release.2\"\nhost enter-chroot -n \"$release.2\" true\nfails host enter-chroot -n \"$release\" true\n\n# Cannot copy a chroot that does not exist\nfails host edit-chroot -y \"$release\" -C \"$release.3\"\n\n# Cannot copy and move a chroot\nfails host edit-chroot -y \"$release\" -C \"$release.3\" -m \"$release.4\"\n\n# Copy a chroot\nhost edit-chroot -y \"$release.2\" -C \"$release.3\"\nhost enter-chroot -n \"$release.2\" true\nhost enter-chroot -n \"$release.3\" true\n\n# Delete the copy\nhost delete-chroot -y \"$release.3\"\nfails host enter-chroot -n \"$release.3\" true\n\n# Restore the chroot\nhost edit-chroot -y -f \"$BACKUPDIR/$release.tar\" -r \"$release\"\nhost enter-chroot -n \"$release\" true\n# Verify croutonversion outputs correct information\nhost edit-chroot -l $release | tee /dev/stderr | passes grep \"^release: $release\"\nrm \"$BACKUPDIR/$release.tar\"\n\n# Back up with a compressed split\nhost edit-chroot -y -s 50 -f \"$BACKUPDIR/$release.tar.gz\" -b \"$release\"\ntest \"`ls \"$BACKUPDIR/$release.tar.gz\"* | wc -l`\" -ge 2\ndu -b \"$BACKUPDIR/$release.tar.gz\"* | passes awk '$1>50*1024*1024{exit 1}'\n# Ensure it's compressed\ntest \"`hexdump -v -n2 -e'\"%02x\"' \"$BACKUPDIR/$release.tar.gz\"`\" = \"8b1f\"\n\n# Restore the split archive. Fails first due to existence\nfails host edit-chroot -y -f \"$BACKUPDIR/$release.tar.gz\" -r \"$release\"\nhost edit-chroot -y -f \"$BACKUPDIR/$release.tar.gz\" -rr \"$release\"\nhost enter-chroot -n \"$release\" true\n\n# Delete it\nhost delete-chroot -y \"$release\"\nfails host enter-chroot -n \"$release\" true\n\n# Restore the chroot using the crouton installer (without update)\ncrouton -f \"$BACKUPDIR/$release.tar.gz\" -n \"$release\"\nhost enter-chroot -n \"$release\" true\nhost edit-chroot -l $release | tee /dev/stderr | passes grep \"^release: $release\"\nrm \"$BACKUPDIR/$release.tar.gz\"*\n\n# Backup a chroot with automatic naming\nhost edit-chroot -y -f \"$BACKUPDIR\" -b \"$release\"\n\n# Delete it\nhost delete-chroot -y \"$release\"\nfails host enter-chroot -n \"$release\" true\n\n# Restore the chroot with automatically named tarball\nhost edit-chroot -y -f \"$BACKUPDIR\" -r \"$release\"\nhost enter-chroot -n \"$release\" true\nrm \"$BACKUPDIR\"/*.tar.*\n\n# Backing up a chroot fails if there if not enough disk space\n# Create a 5MB filesystem\ndd if=/dev/zero of=\"$PREFIX/img\" bs=1M count=0 seek=5\nmkfs.ext2 -F \"$PREFIX/img\"\nmkdir -p \"$PREFIX/img.mnt\"\nmount -o loop \"$PREFIX/img\" \"$PREFIX/img.mnt\"\n# Make sure backup does not complete\nfails host edit-chroot -y -f \"$PREFIX/img.mnt\" -b \"$release\"\nfails ls \"$PREFIX/img.mnt\"/*.tar.*\numount \"$PREFIX/img.mnt\"\nrm -f \"$PREFIX/img\"\n\n# Tests with multiple parameters (we have $release and $release.2 at this stage)\n\n# No archive gets created if one of the chroots does not exist\nfails host edit-chroot -y -f \"$BACKUPDIR\" -b \\\n    \"$release\" \"$release.2\" \"$release.3\"\nfails ls \"$BACKUPDIR\"/*\n\n# Backup both chroots\nhost edit-chroot \"$release\" -y -f \"$BACKUPDIR\" -b \"$release.2\"\n\n# Copy them to a new prefix: destination needs to end with a slash\nfails host edit-chroot -y -C \"$PREFIX/chroots.2\" \"$release\" \"$release.2\"\n\nhost edit-chroot -y -C \"$PREFIX/chroots.2/\" \"$release\" \"$release.2\"\nhost enter-chroot -c \"$PREFIX/chroots.2\" -n \"$release\" true\nhost enter-chroot -c \"$PREFIX/chroots.2\" -n \"$release.2\" true\nhost enter-chroot -n \"$release\" true\nhost enter-chroot -n \"$release.2\" true\n\n# Move them to a new prefix: destination needs to end with a slash\nfails host edit-chroot -y -m \"$PREFIX/chroots.3\" \"$release\" \"$release.2\"\nhost enter-chroot -n \"$release\" true\nhost enter-chroot -n \"$release.2\" true\n\nhost edit-chroot -y -m \"$PREFIX/chroots.3/\" \"$release\" \"$release.2\"\nhost enter-chroot -c \"$PREFIX/chroots.3\" -n \"$release\" true\nhost enter-chroot -c \"$PREFIX/chroots.3\" -n \"$release.2\" true\nfails host enter-chroot -n \"$release\" true\nfails host enter-chroot -n \"$release.2\" true\n\n# Delete all four chroots\nhost delete-chroot \"$release\" \"$release.2\" -y -c \"$PREFIX/chroots.2\"\nhost delete-chroot \"$release\" \"$release.2\" -y -c \"$PREFIX/chroots.3\"\nfails host enter-chroot -c \"$PREFIX/chroots.2\" -n \"$release\" true\nfails host enter-chroot -c \"$PREFIX/chroots.2\" -n \"$release.2\" true\nfails host enter-chroot -c \"$PREFIX/chroots.3\" -n \"$release\" true\nfails host enter-chroot -c \"$PREFIX/chroots.3\" -n \"$release.2\" true\n\nOLDCWD=\"`pwd`\"\n\n# Restore both chroots from automatically named tarballs in current directory\ncd \"$BACKUPDIR\"\nhost edit-chroot -y -r \"$release\" \"$release.2\"\nhost enter-chroot -n \"$release\" true\nhost enter-chroot -n \"$release.2\" true\ncd \"$OLDCWD\"\nrm \"$BACKUPDIR\"/*.tar.*\n\n# chroot name can start with -\nhost edit-chroot -y -m \"-$release\" \"$release.2\"\nhost enter-chroot -n \"-$release\" true\n\n# Check that invalid chroot name is detected in parameters\nfails host edit-chroot -b -y \"test/$release\"\nfails host edit-chroot -b -y \"..\" \"$release.2\"\nfails host edit-chroot -b -y \"$release.2\" \".\"\nfails host edit-chroot -b -y \"$release.2\" \"\"\nfails ls \"$BACKUPDIR\"/*\n\n# Check invalid names are also detected in parameter to -m\n# (/ are acceptable in this context, as target directory)\nfails host edit-chroot -y -m \".\" -- \"-$release\"\nfails host edit-chroot -y -m \"..\" -- \"-$release\"\nfails host edit-chroot -y -m \"\" -- \"-$release\"\nhost enter-chroot -n \"-$release\" true\n\n# Check invalid names are also detected in parameter to -C\n# (/ are acceptable in this context, as target directory)\nfails host edit-chroot -y -C \".\" -- \"-$release\"\nfails host edit-chroot -y -C \"..\" -- \"-$release\"\nfails host edit-chroot -y -C \"\" -- \"-$release\"\nhost enter-chroot -n \"-$release\" true\n\n# Delete both chroots\nhost delete-chroot -y -- \"-$release\" \"$release\"\nfails host enter-chroot -n \"$release\" true\nfails host enter-chroot -n -- \"-$release\" true\n"
  },
  {
    "path": "test/tests/14-background",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Ensure backgrounding works properly and fails when it's supposed to\n# Release-independent.\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\nsnapshot \"$release\" core\n\nwaitfinish() {\n    while ! host unmount-chroot \"$release\"; do\n        sleep 1\n    done\n    # Make sure the chroot isn't being unmounted by the backgrounded process.\n    while pgrep -f unmount-chroot >/dev/null; do\n        sleep 1\n    done\n}\n\n# Should work by default\nhost enter-chroot -b -n \"$release\" true\nwaitfinish\n\n# Return value doesn't matter\nhost enter-chroot -b -n \"$release\" false\nwaitfinish\n\n# Piping should work\ns='PIPES ARE GO'\necho \"$s\" | host enter-chroot -b -n \"$release\" cat | passes grep \"$s\" | log\n\n# Groups can still be created/modified\nhost enter-chroot -n \"$release\" -u 0 groupdel hwaudio\nfails grep hwaudio \"$PREFIX/chroots/$release/etc/group\"\nhost enter-chroot -b -n \"$release\" sleep 3\n# group must be recreated before the command goes to background\npasses grep hwaudio \"$PREFIX/chroots/$release/etc/group\"\nwaitfinish\n\n# Even with a failing rc.local it should work\nlog 'Making rc.local fail'\nrclocal=\"$PREFIX/chroots/$release/etc/rc.local\"\necho '#!/bin/sh -e\nexit 1' > \"$rclocal\"\nchmod a+rx \"$rclocal\"\nhost enter-chroot -b -n \"$release\" true\nwaitfinish\n# Remove the failing rc.local: it should also work without rc.local\nrm -f \"$rclocal\"\nhost enter-chroot -b -n \"$release\" true\nwaitfinish\n\n# Even with a failing dbus-daemon it should work\nlog 'Making dbus-daemon fail'\nfor dir in bin sbin usr/bin usr/sbin; do\n    dbusdaemon=\"$PREFIX/chroots/$release/$dir/dbus-daemon\"\n    if [ -d \"`dirname \"$dbusdaemon\"`\" ]; then\n        echo '#!/bin/sh -e\nexit 1' > \"$dbusdaemon\"\n        chmod a+rx \"$dbusdaemon\"\n    fi\ndone\nhost enter-chroot -b -n \"$release\" true\nwaitfinish\n"
  },
  {
    "path": "test/tests/15-media",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Ensure media sharing works with the host OS.\n\n# Release-independent.\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\nsnapshot \"$release\" core\n\n# Creates a fake but representative media device with a picture of a cat on it.\n# Returns the path in the variable \"newmediadevice\"\ntestfile='cat.jpg'\naddmediadevice() {\n    newmediadevice=\"`mktemp -d --tmpdir='/media/removable' testmount.XXXXXXXX`\"\n    addtrap \"umount -l '$newmediadevice' 2>/dev/null; rmdir '$newmediadevice'\"\n    mount -t tmpfs catstash \"$newmediadevice\"\n    touch \"$newmediadevice/$testfile\"\n}\n\n# Add a media device that should persist throughout the whole test\naddmediadevice\npersist=\"$newmediadevice\"\n\n# The device should exist inside the chroot at the same locaton\nhost enter-chroot -n \"$release\" test -f \"$persist/$testfile\"\n\n# The device should still exist after the above unmounts\ntest -f \"$persist/$testfile\"\n\n# Run the chroot in the background to maintain the link\nhost enter-chroot -b -n \"$release\" sh -ec \"\n    while [ -f '$persist/$testfile' ]; do\n        sleep 1\n    done\"\n\n# Now that the chroot is mounted, creating another mount should appear.\naddmediadevice\nnonpersist=\"$newmediadevice\"\n\n# The device should exist inside the chroot at the same locaton\nhost enter-chroot -n \"$release\" test -f \"$nonpersist/$testfile\"\n\n# Unmounting the device should make it disappear from the chroot\numount \"$nonpersist\"\nhost enter-chroot -n \"$release\" test ! -f \"$nonpersist/$testfile\"\n\n# Adding the device and unmounting it inside the chroot should work as well.\naddmediadevice\nnonpersist=\"$newmediadevice\"\nhost enter-chroot -n \"$release\" -u root umount \"$nonpersist\"\ntest ! -f \"$nonpersist/$testfile\"\n\n# Lastly, adding a device inside the chroot should appear outside of it.\nhost enter-chroot -n \"$release\" -u root sh -ec \"\n    mount -t tmpfs catstash '/var/host$nonpersist'\n    touch '$nonpersist/$testfile'\"\ntest -f \"$nonpersist/$testfile\"\n\n# Confirm the persistent one still exists, and finish it off\ntest -f \"$persist/$testfile\"\numount \"$persist\"\n\n# Wait for the chroot to exit\nsleep 1\nwhile ! host unmount-chroot \"$release\"; do\n    sleep 1\ndone\n"
  },
  {
    "path": "test/tests/16-targetsfile",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Make sure we detect if /etc/crouton/targets is not present (e.g. update from\n# an old chroot), and that we can recover the situation by specifying targets\n# manually.\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\nchecktargetlist() {\n    # Target list must include core only\n    test \"$(host enter-chroot -n \"$release\" cat /etc/crouton/targets \\\n                | tr -d ',\\n')\" = 'core'\n}\n\n# Release-independent.\nsnapshot \"$release\" core\n\nhost enter-chroot -n \"$release\" true\nchecktargetlist\n\n# Update works\ncrouton -n \"$release\" -u\nchecktargetlist\n\n# Update fails if /etc/crouton/targets is deleted\nhost enter-chroot -n \"$release\" -u 0 rm /etc/crouton/targets\nfails checktargetlist\nfails crouton -n \"$release\" -u\nfails checktargetlist\n\n# But we can recover the chroot by specifying targets\ncrouton -n \"$release\" -u -t core\nchecktargetlist\n"
  },
  {
    "path": "test/tests/17-encryption",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Ensure all aspects of encryption work properly.\n\nif [ -z \"$release\" ]; then\n    echo \"default\"\n    exit 0\nfi\n\n# Create encrypted chroot\ncrouton -f \"`bootstrap \"$release\"`\" -e -t core\n\n# Ensure all files are encrypted\nfor file in \"$PREFIX/chroots/$release\"/*; do\n    test \"${file#*/ECRYPTFS}\" != \"$file\"\ndone\n\n# Check chroot is encrypted and locked\nhost edit-chroot -l \"$release\" | passes grep \"^encrypted: yes, locked\"\n\n# Unlocked after mounted\nhost mount-chroot \"$release\"\nhost edit-chroot -l \"$release\" | passes grep \"^encrypted: yes, unlocked\"\n\n# Locked again if unmounted\nhost unmount-chroot \"$release\"\nhost edit-chroot -l \"$release\" | passes grep \"^encrypted: yes, locked\"\n\n# Enter encrypted chroot\nhost enter-chroot -n \"$release\" true\n\n# Incorrect password on encrypted chroot\ncorrect=\"$CROUTON_PASSPHRASE\"\nexport CROUTON_PASSPHRASE=wrongfuelcelldonkeyclip\nfails host enter-chroot -n \"$release\" true\n\n# Change encrypted chroot passphrase\nexport CROUTON_PASSPHRASE=\"$correct\"\nexport CROUTON_NEW_PASSPHRASE='correcthorsebatterystable'\nhost edit-chroot -e \"$release\"\nexport CROUTON_PASSPHRASE=\"$CROUTON_NEW_PASSPHRASE\"\nhost enter-chroot -n \"$release\" true\n\n# Move key to separate file\nhost edit-chroot -k \"$PREFIX/$release.key\" \"$release\"\ntest -f \"$PREFIX/$release.key\"\nhost enter-chroot -n \"$release\" true\n\n# Move key to a relative location\noldpwd=\"$PWD\"\ncd \"$PREFIX\"\nhost edit-chroot -k \"$release.relative.path\" \"$release\"\ntest -f \"$release.relative.path\"\ncd /\nhost enter-chroot -n \"$release\" true\ncd \"$oldpwd\"\n\n# Move key to a folder\nmkdir \"$PREFIX/keys\"\nhost edit-chroot -k \"$PREFIX/keys\" \"$release\"\ntest -f \"$PREFIX/keys/$release\"\nhost enter-chroot -n \"$release\" true\n\n# Move key back to inside chroot\nhost edit-chroot -k - \"$release\"\ntest ! -f \"$PREFIX/keys/$release\"\nhost enter-chroot -n \"$release\" true\n\n# Move key over another file (fail)\ntouch \"$PREFIX/keys/exists\"\nfails host edit-chroot -k \"$PREFIX/keys/exists\" \"$release\"\n\n# Move key to a folder with a file already there (fail)\ntouch \"$PREFIX/keys/$release\"\nfails host edit-chroot -k \"$PREFIX/keys\" \"$release\"\n\n# Ensure you can update an encrypted chroot\ncrouton -u -n \"$release\"\n\n# Create encrypted chroot with external key where a file exists (fail)\nfails crouton -f \"`bootstrap \"$release\"`\" -e -t core -n \"$release-2\" \\\n              -k \"$PREFIX/keys/exists\"\n\n# Create encrypted chroot with external key where folder with file exists (fail)\ntouch \"$PREFIX/keys/$release-2\"\nfails crouton -f \"`bootstrap \"$release\"`\" -e -t core -n \"$release-2\" \\\n              -k \"$PREFIX/keys\"\n\n# Create encrypted chroot with external key\nrm \"$PREFIX/keys/\"*\ncrouton -f \"`bootstrap \"$release\"`\" -e -t core -n \"$release-2\" \\\n        -k \"$PREFIX/keys/exists\"\nhost enter-chroot -n \"$release-2\" true\n\n# Move multiple keys into new folder\nhost edit-chroot -k \"$PREFIX/keys\" \"$release\" \"$release-2\"\nhost enter-chroot -n \"$release\" true\nhost enter-chroot -n \"$release-2\" true\n\n# Physically move key and be unable to enter chroot\nmv \"$PREFIX/keys/$release\" \"$PREFIX/keys/exists\"\nfails host enter-chroot -n \"$release\" true\n\n# Specify new key location and be able to enter again\nhost enter-chroot -n \"$release\" -k \"$PREFIX/keys/exists\" true\nmv \"$PREFIX/keys/exists\" \"$PREFIX/keys/$release\"\n\n# Move multiple keys back to chroot\nhost edit-chroot -k - \"$release\" \"$release-2\"\ntest ! -f \"$PREFIX/keys/$release\" -a ! -f \"$PREFIX/keys/$release-2\"\n\n# Remove the encrypted chroots\nhost delete-chroot -y \"$release\" \"$release-2\"\n\n# Create unencrypted chroot\nsnapshot \"$release\" core\n\n# Encrypt the chroot\nhost edit-chroot -e \"$release\"\nhost enter-chroot -n \"$release\" true\n\n# Ensure all files are encrypted\nfor file in \"$PREFIX/chroots/$release\"/*; do\n    test \"${file#*/ECRYPTFS}\" != \"$file\"\ndone\n\n# Add some unencrypted files, test \"yes\"/\"no\"/\"del\"/\"list\" responses\nchroot=\"$PREFIX/chroots/$release\"\nmkdir -p \"$chroot/a/b\"\ntouch \"$chroot/a/x\" \"$chroot/a/b/y\"\n\n# No => they still exist\nexport CROUTON_MOUNT_RESPONSE='no'\nhost enter-chroot -n \"$release\" true\ntest -f \"$chroot/a/x\" -a -f \"$chroot/a/b/y\"\n\n# List => we get a list; command fails with exit code 2; files still exist\nexport CROUTON_MOUNT_RESPONSE='list'\nexitswithin 2 30 host enter-chroot -n \"$release\" true 2>&1 \\\n    | tee /dev/stderr | passes grep '^/a$'\ntest -f \"$chroot/a/x\" -a -f \"$chroot/a/b/y\"\n\n# Delete => they no longer exist inside or outside the chroot\nexport CROUTON_MOUNT_RESPONSE='delete'\nhost enter-chroot -n \"$release\" test ! -d '/a'   -a ! -d '/a/b' \\\n                                  -a ! -f '/a/x' -a ! -f '/a/b/y'\ntest ! -d \"$chroot/a\"   -a ! -d \"$chroot/a/b\" \\\n  -a ! -f \"$chroot/a/x\" -a ! -f \"$chroot/a/b/y\"\n\n# Yes => they exist inside the chroot but not out\nexport CROUTON_MOUNT_RESPONSE='yes'\nmkdir -p \"$chroot/a/b\"\ntouch \"$chroot/a/x\" \"$chroot/a/b/y\"\nhost enter-chroot -n \"$release\" test -d '/a'   -a -d '/a/b' \\\n                                  -a -f '/a/x' -a -f '/a/b/y'\ntest ! -d \"$chroot/a\"   -a ! -d \"$chroot/a/b\" \\\n  -a ! -f \"$chroot/a/x\" -a ! -f \"$chroot/a/b/y\"\n"
  },
  {
    "path": "test/tests/18-upgrade",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Test upgrade path from Ubuntu saucy to trusty.\n# Also test precise->trusty (LTS, upgrade not suggested until July 2014)\n\n# Format: FIRST;NEXT;DEV;PROMPT\n# FIRST: release to start with\n# NEXT: list of releases to be upgraded to\n# DEV: one additional release, for which upgrading is not recommended yet\n# PROMPT: choose normal or lts upgrade\n\nUPGRADEPATHS=\"\\\nprecise;trusty;;lts\"\n\n#FIXME: add trusty;;utopic;normal then this is fixed:\n# https://bugs.launchpad.net/ubuntu/+source/update-manager-core/+bug/1382707\n\nif [ -z \"$release\" ]; then\n    # Only test upgrade if no release was set explicitly\n    if [ -z \"$SUPPORTED_RELEASES_SET\" ]; then\n        # Output all releases that are first in the upgrade paths\n        echo \"$UPGRADEPATHS\" | while read -r line; do\n            echo \"${line%%;*}\"\n        done\n    fi\n    exit 0\nfi\n\n# Install critical targets that are likely to break, as they manually pull in\n# packages from the mirror, or install alternative packages.\n\nTARGETS=\"core,audio,touch,x11\"\nPACKAGES=\"touchegg\"\n\n# upgrade [-d] release\n# -d: upgrade to a development release\n# release: expected release after the upgrade\nupgrade() {\n    extra=\n    if [ \"$1\" = \"-d\" ]; then\n        extra=\"$1\"\n        shift\n    fi\n    host enter-chroot -n \"$FIRST\" -u 0 do-release-upgrade $extra \\\n                        -f DistUpgradeViewNonInteractive\n    passes sh -e \"$SCRIPTDIR/installer/ubuntu/getrelease.sh\" \\\n                        -r \"$PREFIX/chroots/$FIRST\" | passes grep -q \"^$1$\"\n    crouton -n \"$FIRST\" -u\n    versioninfo\n}\n\n# Dump version of critical packages\nversioninfo() {\n    host enter-chroot -n \"$FIRST\" -u 0 sh -ec '\n            sed -n -e \"s/DISTRIB_CODENAME=/=== /p\" /etc/lsb-release\n            dpkg-query -W '\"$PACKAGES\"'\n        ' | log\n}\n\n# Test upgrade paths starting with the selected release\necho \"$UPGRADEPATHS\" | grep \"^$release;\" | while read -r line; do\n    FIRST=\"${line%%;*}\"\n    line=\"${line#$FIRST;}\"\n    NEXT=\"${line%%;*}\"\n    line=\"${line#$NEXT;}\"\n    DEV=\"${line%%;*}\"\n    line=\"${line#$DEV;}\"\n    PROMPT=\"${line%%;*}\"\n\n    log \"Trying upgrade path FIRST='$FIRST' to NEXT='$NEXT' (DEV='$DEV'). PROMPT='$PROMPT'\"\n\n    crouton -T -r \"$FIRST\" -t \"$TARGETS\" <<EOF\ninstall update-manager-core python-apt\nsed -i -e 's/Prompt=lts/Prompt=$PROMPT/' /etc/update-manager/release-upgrades\nEOF\n    versioninfo\n\n    for next in $NEXT; do\n        upgrade \"$next\"\n    done\n\n    # Then upgrade to latest development version as well\n    if [ -n \"$DEV\" ]; then\n        upgrade -d \"$DEV\"\n    fi\n\n    host delete-chroot \"$FIRST\"\ndone\n"
  },
  {
    "path": "test/tests/20-logind",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"trusty\"\n    echo \"utopic\"\n    exit 0\nfi\n\n# Need dbus in cli-extra to bring in systemd-services.\nsnapshot \"$release\" cli-extra\n\n# logind should be running.\nhost enter-chroot -n \"$release\" -u root pidof -c systemd-logind\n\n# systemd cgroup should be set up.\nhost enter-chroot -n \"$release\" test -f /sys/fs/cgroup/systemd/cgroup.procs\n\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/30-audio",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# All supported releases should be able to create an audio chroot and play sound\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\nsnapshot \"$release\" audio\n\n# We pass -fdat to aplay/arecord, which means 48kHz, 16-bit, stereo.\n# dd writes/reads 8 blocks of 48000 bytes: 2 seconds worth of sound.\nexitswithin 0 30 host enter-chroot -n \"$release\" sh -exc '\n    cras_test_client --dump_server_info\n    dd if=/dev/zero bs=48000 count=8 | aplay -fdat -v\n    arecord -fdat -v | dd of=/dev/null bs=48000 count=8 iflag=fullblock\n'\n\n# On x86_64 systems, test x86 client\nif [ \"`uname -m`\" = \"x86_64\" ]; then\n    echo '\n        mkdir -p /home/tmp\n        if [ \"${DISTROAKA:-\"$DISTRO\"}\" = \"debian\" ]; then\n            (\n                cd /home/tmp\n                apt-get download alsa-utils:i386\n                dpkg -x alsa-utils_*_i386.deb .\n                mv usr/bin/* .\n            )\n        else\n            # Other distributions must be implemented or explicitly ignored\n            error 1 \"Cannot fetch alsa-utils:i386 for $DISTRO\"\n        fi\n    ' | crouton -T -U -n \"$release\"\n\n    exitswithin 0 30 host enter-chroot -n \"$release\" sh -exc '\n        /usr/local/i386-linux-gnu/bin/cras_test_client --dump_server_info\n        dd if=/dev/zero bs=48000 count=8 | \\\n                /home/tmp/aplay -fdat -v\n        /home/tmp/arecord -fdat -v | \\\n                dd of=/dev/null bs=48000 count=8 iflag=fullblock\n    '\nfi\n\necho 'install pulseaudio pulseaudio-utils' | \\\n    crouton -T -U -n \"$release\"\nexitswithin 0 30 host enter-chroot -n \"$release\" sh -exc '\n    pulseaudio --start\n    dd if=/dev/zero bs=48000 count=8 | pacat -v\n    dd if=/dev/zero bs=48000 count=8 | aplay -f dat -Dpulse -v\n    arecord -fdat -Dpulse -v | \\\n        dd if=/dev/null bs=48000 count=8 iflag=fullblock\n    cras_test_client --dump_server_info\n    pulseaudio --kill\n'\n\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/35-xorg",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# All supported releases should be able to launch an xorg server\nif [ -z \"$release\" ]; then\n    # xorg does not work well on wheezy\n    for rel in $SUPPORTED_RELEASES; do\n        if [ \"$rel\" != wheezy ]; then\n            echo \"$rel\"\n        fi\n    done\n    exit 0\nfi\n\nsnapshot \"$release\" audio\ncrouton -U -n \"$release\" -t xorg\n\necho '\n    install mesa-utils\n    mkdir -p /home/tmp\n    chmod 777 /home/tmp\n    cat > /home/tmp/xinitrc <<END\n#!/bin/sh\nset -x\nglxinfo 2>&1 | tee /home/tmp/glxinfo.out\nxdriinfo 2>&1 | tee /home/tmp/xdriinfo.out\nexec /usr/bin/xterm -e true\nEND\n' | crouton -T -U -n \"$release\"\n\nvtlockrun exitswithin 0 60 host \\\n    enter-chroot -n \"$release\" exec xinit \"/home/tmp/xinitrc\"\nhost enter-chroot -n \"$release\" sh -c '\n    echo \"====GLX info\"\n    echo -n \"release:\"\n    croutonversion -r\n    echo -n \"uname:\"\n    uname -a\n    echo -n \"vendor:\"\n    cat /sys/class/graphics/fb0/device/vendor\n    echo -n \"device:\"\n    cat /sys/class/graphics/fb0/device/device\n    echo -n \"xdriinfo:\"\n    cat /home/tmp/xdriinfo.out\n    echo \"==glxinfo\"\n    cat /home/tmp/glxinfo.out\n    echo \"====/GLX info\"\n' | log\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/37-xiwi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# All supported releases should be able to launch an xiwi server\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\n# FIXME: This test is incomplete, as it is not able to connect to the\n# extension, so xinit parameter is never run. However, this tests target\n# installation, and verifies that there is no critical X failures (e.g.\n# X server cannot start).\n\nsnapshot \"$release\" audio\ncrouton -U -n \"$release\" -t xiwi\n# FIXME: When x11test is merged, this should be run under vtlock\nexitswithin 0 60 host \\\n    enter-chroot -n \"$release\" exec xinit /usr/bin/xterm -e true\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/w0-common",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This test is sourced by other w* test, do not run it on its own\nif [ -z \"$release\" -o -z \"$target\" ]; then\n    exit 0\nfi\n\nif [ -z \"$startcmd\" ]; then\n    error 2 \"startcmd not defined\"\nfi\n\n# Start with a x11 snapshot\nsnapshot \"$release\" x11\n\n# Install snapshot tools + xte\necho 'install --minimal x11-apps imagemagick xautomation' | \\\n    crouton -T -U -n \"$release\"\n\nret=0\ncrouton -u -n \"$release\" -t \"$target\" || ret=$?\nif [ \"$ret\" -ne 0 ]; then\n    if [ \"$ret\" -eq 99 ]; then\n        log \"Target $target failed on $release, as expected (unsupported combination).\"\n        exit 0\n    else\n        log \"Target $target failed on $release.\"\n        exit \"$ret\"\n    fi\nfi\n\n(\n    vtlock\n    host \"$startcmd\" -b -n \"$release\"\n    host enter-chroot -n \"$release\" sh -exc '\n        timeout=60\n        while [ \"$timeout\" -gt 0 ]; do\n            timeout=\"$((timeout - 5))\"\n            sleep 5\n            DISPLAY=\"`croutoncycle display`\"\n            if [ \"$DISPLAY\" != \"aura\" -a \"$DISPLAY\" != \":0\" ]; then\n                break\n            fi\n        done\n        # Test croutoncycle as a bonus\n        if [ \"$DISPLAY\" = \":0\" -o \"$DISPLAY\" = \"aura\" ]; then\n            echo \"Invalid display ($DISPLAY).\" 1>&2\n            exit 1\n        fi\n        # Let WM/DE settle, then play xte sequence\n        sleep 30\n        export DISPLAY\n        if [ -n \"'\"$xte\"'\" ]; then\n            echo \"'\"$xte\"'\" | tr \";\" \"\\n\" | xte\n        fi\n        # Let WM/DE settle again\n        sleep 30\n        # Snapshot! We use xwd, as import does not snapshot obscured windows\n        # correctly (dialogs end up as black squares)\n        xwd -root | convert -quality 75 xwd:- ~/\"screenshot-'\"$target\"'.jpg\"'\n\n    mv \"$PREFIX/chroots/$release/home/test/screenshot-$target.jpg\" \\\n        \"$file-snapshot.jpg\"\n\n    host unmount-chroot -f -y \"$release\"\n)\n\nhost delete-chroot -y \"$release\"\n"
  },
  {
    "path": "test/tests/w1-e17",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"e17\"\nstartcmd=\"starte17\"\n# FIXME: Unfortunately, e17 cannot be configured with the keyboard only...\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w2-gnome",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"gnome\"\nstartcmd=\"startgnome\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w2d-gnome-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"gnome-desktop\"\nstartcmd=\"startgnome\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w3-kde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"kde\"\nstartcmd=\"startkde\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w3d-kde-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"kde-desktop\"\nstartcmd=\"startkde\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w4-lxde",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"lxde\"\nstartcmd=\"startlxde\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w4d-lxde-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"lxde-desktop\"\nstartcmd=\"startlxde\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w5-unity",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"unity\"\nstartcmd=\"startunity\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w5d-unity-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"unity-desktop\"\nstartcmd=\"startunity\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w6-kodi",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"kodi\"\nstartcmd=\"startkodi\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w7-xfce",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"xfce\"\nstartcmd=\"startxfce4\"\n# This magic sequence presses \"Use default configuration\"\nxte=\"keydown Alt_L;key Tab;key Tab;keyup Alt_L;key Left;key Left;\\\nkeydown Return;sleep 1;keyup Return\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/w7d-xfce-desktop",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\ntarget=\"xfce-desktop\"\nstartcmd=\"startxfce4\"\nxte=\"\"\n\n# Run common file\n. \"$SCRIPTDIR/test/tests/w0-common\"\n"
  },
  {
    "path": "test/tests/x0-alltargets",
    "content": "#!/bin/sh -e\n# Copyright (c) 2016 The crouton Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nif [ -z \"$release\" ]; then\n    echo \"all\"\n    exit 0\nfi\n\n# All supported releases should be able to install all targets, unless\n# explicitly blacklisted (exit status 99)\nfor target in \"$SCRIPTDIR/targets/\"*; do\n    target=\"${target#$SCRIPTDIR/targets/}\"\n\n    # Ignore *common targets\n    if [ \"${target%common}\" != \"$target\" ]; then\n        continue\n    fi\n\n    # Ignore *desktop targets too, since they'd all be blacklisted\n    if [ \"${target%desktop}\" != \"$target\" ]; then\n        continue\n    fi\n\n    # Some other targets do not require testing in this context,\n    # or have their own w* tests\n    for blacklist in audio core x11 xiwi xorg \\\n                     e17 gnome kde lxde unity xbmc xfce; do\n        if [ \"$target\" = \"$blacklist\" ]; then\n            break\n        fi\n    done\n    if [ \"$target\" = \"$blacklist\" ]; then\n        continue\n    fi\n\n    # Start with a x11 snapshot, as most targets depend on it\n    snapshot \"$release\" x11\n    ret=0\n    crouton -u -n \"$release\" -t \"$target\" || ret=$?\n    if [ \"$ret\" -ne 0 ]; then\n        if [ \"$ret\" -eq 99 ]; then\n            log \"Target $target failed on $release, as expected (unsupported combination).\"\n        else\n            log \"Target $target failed on $release.\"\n            exit \"$ret\"\n        fi\n    fi\n    host delete-chroot -y \"$release\"\ndone\n"
  }
]