// ---------------------------------------------------------------------- // File: com_squash.cc // Author: Andreas-Joachim Peters - CERN // ---------------------------------------------------------------------- /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2018 CERN/Switzerland * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "console/ConsoleMain.hh" #include "common/StringTokenizer.hh" #include "common/StringConversion.hh" #include "common/Path.hh" #include "XrdPosix/XrdPosixXrootd.hh" #include "XrdOuc/XrdOucEnv.hh" #include "pwd.h" #include #include /* List a directory */ int com_squash(char* arg1) { eos::common::StringTokenizer subtokenizer(arg1); subtokenizer.GetLine(); XrdOucString cmd = ""; XrdOucString path = ""; XrdOucString option = ""; XrdOucString fulloption = ""; bool ok = false; const int len = 4096; char username[len]; struct passwd* pw = getpwuid(geteuid()); if (pw == nullptr) { fprintf(stderr, "error: failed to get effective UID username of calling " "process\n"); goto com_squash_usage; } (void) strncpy(username, pw->pw_name, len - 1); username[len - 1] = '\0'; do { cmd = subtokenizer.GetToken(); path = subtokenizer.GetToken(); if (!cmd.length()) { goto com_squash_usage; } if (cmd == "--help") { goto com_squash_usage; } if (cmd == "-h") { goto com_squash_usage; } if (path.length() && (path[0] == '-')) { option = path[1]; fulloption = path; path = subtokenizer.GetToken(); } if (!path.length()) { goto com_squash_usage; } if ((cmd != "trim-release") && (cmd != "new-release")) { XrdOucString garbage = subtokenizer.GetToken(); if (garbage.length()) { goto com_squash_usage; } else { break; } } else { break; } } while (1); path = abspath(path.c_str()); if (cmd == "new") { struct stat buf; eos::common::Path packagepath(path.c_str()); if (!stat(packagepath.GetPath(), &buf)) { fprintf(stderr, "error: package path='%s' exists already\n", packagepath.GetPath()); global_retc = EEXIST; return (0); } std::string mkpath = "/var/tmp/"; mkpath += username; mkpath += "/eosxd/mksquash/"; mkpath += packagepath.GetContractedPath(); mkpath += "/dummy"; eos::common::Path mountpath(mkpath.c_str()); if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP)) { fprintf(stderr, "error: failed to create local mount point path='%s'\n", mountpath.GetParentPath()); global_retc = errno; return (0); } if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) { fprintf(stderr, "error: failed to create symbolic link from '%s' => '%s'\n", mountpath.GetParentPath(), packagepath.GetPath()); global_retc = errno; return (0); } ok = true; fprintf(stderr, "info: ready to install your software under '%s'\n", packagepath.GetPath()); fprintf(stderr, "info: when done run 'eos squash pack %s' to create an " "image file and a smart link in EOS!\n", packagepath.GetPath()); } if (cmd == "install") { if (fulloption.beginswith("--curl=")) { ok = true; std::string url = fulloption.c_str() + 7; if (fulloption.endswith(".tgz") || fulloption.endswith(".tar.gz")) { int sub_rc = 0; // squash rm std::string subcommand = "rm \""; subcommand += path.c_str(); subcommand += "\""; com_squash((char*)subcommand.c_str()); sub_rc |= global_retc; // squash new subcommand = "new \""; subcommand += path.c_str(); subcommand += "\""; com_squash((char*)subcommand.c_str()); sub_rc |= global_retc; // download std::string shellcmd = "cd \""; shellcmd += path.c_str(); shellcmd += "\";"; shellcmd += "curl "; shellcmd += url; shellcmd += " /dev/stdout | "; shellcmd += "tar xvzf -"; int rc = system(shellcmd.c_str()); if (WEXITSTATUS(rc)) { fprintf(stderr, "error: curl download failed with retc='%d'\n", WEXITSTATUS(rc)); global_retc = WEXITSTATUS(rc); return (0); } // squash pack subcommand = "pack \""; subcommand += path.c_str(); subcommand += "\""; com_squash((char*)subcommand.c_str()); sub_rc |= global_retc; if (sub_rc) { global_retc = sub_rc; return 0; } } else { fprintf(stderr, "error: suffix of '%s' is not supported\n", url.c_str()); global_retc = EINVAL; return (0); } } else { goto com_squash_usage; } } if (cmd == "pack") { eos::common::Path packagepath(path.c_str()); std::string squashpack = packagepath.GetParentPath(); squashpack += "."; squashpack += packagepath.GetName(); squashpack += ".sqsh"; std::string shellcmd = "mksquashfs "; char linktarget[4096]; memset(linktarget, 0, sizeof(linktarget)); ssize_t rl; // resolve symlink if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) { fprintf(stderr, "error: failed to resolve symbolic link of squashfs package '%s'\n - errno '%d'", packagepath.GetPath(), errno); global_retc = errno; return (0); } else { linktarget[rl] = 0; } struct stat buf; if (stat(linktarget, &buf)) { fprintf(stderr, "error: cannot find local package directory '%s'\n", linktarget); global_retc = errno; return (0); } shellcmd += linktarget; shellcmd += " "; shellcmd += squashpack; shellcmd += "~"; shellcmd += " -noappend"; shellcmd += " -force-uid "; shellcmd += std::to_string(geteuid()); shellcmd += " -force-gid "; shellcmd += std::to_string(getegid()); shellcmd += " && mv -f -T "; shellcmd += squashpack; shellcmd += "~"; shellcmd += " "; shellcmd += squashpack; fprintf(stderr, "running %s\n", shellcmd.c_str()); int rc = system(shellcmd.c_str()); if (WEXITSTATUS(rc)) { fprintf(stderr, "error: mksquashfs failed with retc='%d'\n", WEXITSTATUS(rc)); global_retc = WEXITSTATUS(rc); return (0); } else { if (option != "f") { if (unlink(packagepath.GetPath())) { fprintf(stderr, "error: failed to unlink locally staged squashfs archive '%s' - errno '%d'\n", squashpack.c_str(), errno); global_retc = errno; return (0); } else { std::string targetline = "eosxd get eos.hostport "; targetline += packagepath.GetParentPath(); std::string hostport = eos::common::StringConversion::StringFromShellCmd( targetline.c_str()); if (!hostport.length()) { fprintf(stderr, "error: failed to get eos.hostport from mountpoint '%s'\n", targetline.c_str()); global_retc = EIO; return (0); } std::string target = "/eos/squashfs/"; target += hostport; target += "@"; XrdOucString spackagepath = squashpack.c_str(); while (spackagepath.replace("/", "---")) {} target += spackagepath.c_str(); if (symlink(target.c_str(), packagepath.GetPath())) { fprintf(stderr, "error: failed to create squashfs symlink '%s' => '%s'\n", packagepath.GetPath(), target.c_str()); } } } } ok = true; } if (cmd == "relabel") { ok = true; struct stat buf; eos::common::Path packagepath(path.c_str()); std::string squashpack = packagepath.GetParentPath(); squashpack += "."; squashpack += packagepath.GetName(); squashpack += ".sqsh"; if (stat(squashpack.c_str(), &buf)) { fprintf(stderr, "error: the squashfs package file is missing for this label!\n"); global_retc = ENOENT; return (0); } if (!lstat(packagepath.GetPath(), &buf)) { if (unlink(packagepath.GetPath())) { fprintf(stderr, "error: failed to remove existing squashfs archive '%s' - errno '%d'\n", packagepath.GetPath(), errno); global_retc = errno; return (0); } } std::string targetline = "eosxd get eos.hostport "; targetline += packagepath.GetParentPath(); std::string hostport = eos::common::StringConversion::StringFromShellCmd( targetline.c_str()); if (!hostport.length()) { fprintf(stderr, "error: failed to get eos.hostport from mountpoint '%s'\n", targetline.c_str()); global_retc = EIO; return (0); } std::string target = "/eos/squashfs/"; target += hostport; target += "@"; XrdOucString spackagepath = squashpack.c_str(); while (spackagepath.replace("/", "---")) {} target += spackagepath.c_str(); if (symlink(target.c_str(), packagepath.GetPath())) { fprintf(stderr, "error: failed to create squashfs symlink '%s' => '%s'\n", packagepath.GetPath(), target.c_str()); } } if (cmd == "unpack") { ok = true; eos::common::Path packagepath(path.c_str()); std::string squashpack = packagepath.GetParentPath(); squashpack += "."; squashpack += packagepath.GetName(); squashpack += ".sqsh"; char linktarget[4096]; ssize_t rl; // resolve symlink if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) { fprintf(stderr, "error: failed to resolve symbolic link of squashfs package '%s'\n - errno '%d'", packagepath.GetPath(), errno); global_retc = errno; return (0); } else { linktarget[rl] = 0; } XrdOucString mounttarget = linktarget; std::string mkpath = "/var/tmp/"; mkpath += username; mkpath += "/eosxd/mksquash/"; if (option != "f") { if (mounttarget.beginswith(mkpath.c_str())) { fprintf(stderr, "error: squash image is already unpacked!\n"); global_retc = EINVAL; return (0); } if (!geteuid()) { // remove any mounts - only possible as root std::string umountcmd = "umount -f -l "; umountcmd += mounttarget.c_str(); (void) !system(umountcmd.c_str()); if (rmdir(mounttarget.c_str())) { if (errno != ENOENT) { fprintf(stderr, "error: failed to unlink local mount directory path='%s' errno=%d\n", mounttarget.c_str(), errno); } } } } std::string shellcmd = "unsquashfs -f -d "; mkpath += packagepath.GetContractedPath(); mkpath += "/dummy"; eos::common::Path mountpath(mkpath.c_str()); if (!mountpath.MakeParentPath(S_IRWXU | S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP)) { fprintf(stderr, "error: failed to create local mount point path='%s'\n", mountpath.GetParentPath()); global_retc = errno; return (0); } if (unlink(packagepath.GetPath())) { fprintf(stderr, "error: failed to unlink smart link for squashfs archive '%s' - errno '%d'\n", squashpack.c_str(), errno); global_retc = errno; return (0); } if (symlink(mountpath.GetParentPath(), packagepath.GetPath())) { fprintf(stderr, "error: failed to create symbolic link from '%s' => '%s'\n", mountpath.GetParentPath(), packagepath.GetPath()); global_retc = errno; return (0); } shellcmd += mountpath.GetParentPath(); shellcmd.erase(shellcmd.length() - 1); shellcmd += "~"; shellcmd += " "; shellcmd += squashpack.c_str(); shellcmd += " && rsync -aq --delete "; shellcmd += mountpath.GetParentPath(); shellcmd.erase(shellcmd.length() - 1); shellcmd += "~/"; shellcmd += " "; shellcmd += mountpath.GetParentPath(); shellcmd += " && rm -rf "; shellcmd += mountpath.GetParentPath(); shellcmd.erase(shellcmd.length() - 1); shellcmd += "~"; fprintf(stdout, "%s\n", shellcmd.c_str()); int rc = system(shellcmd.c_str()); if (WEXITSTATUS(rc)) { fprintf(stderr, "error: unsquashfs failed with retc='%d'\n", WEXITSTATUS(rc)); global_retc = WEXITSTATUS(rc); return (0); } else { fprintf(stderr, "info: squashfs image is available unpacked under '%s'\n", packagepath.GetPath()); fprintf(stderr, "info: when done with modifications run 'eos squash pack %s' to create an image file and a smart link in EOS!\n", packagepath.GetPath()); } } if (cmd == "info") { ok = true; eos::common::Path packagepath(path.c_str()); std::string squashpack = packagepath.GetParentPath(); squashpack += "."; squashpack += packagepath.GetName(); squashpack += ".sqsh"; struct stat buf; if (!stat(squashpack.c_str(), &buf)) { fprintf(stderr, "info: '%s' has a squashfs image with size=%lu bytes\n", squashpack.c_str(), (unsigned long)buf.st_size); } else { fprintf(stderr, "info: '%s' has no squashfs image\n", squashpack.c_str()); } char linktarget[4096]; ssize_t rl; // resolve symlink if ((rl = readlink(packagepath.GetPath(), linktarget, sizeof(linktarget))) == -1) { fprintf(stderr, "error: failed to resolve symbolic link of squashfs package '%s'\n - errno '%d'", packagepath.GetPath(), errno); global_retc = errno; return (0); } else { linktarget[rl] = 0; } XrdOucString mounttarget = linktarget; std::string mkpath = "/var/tmp/"; mkpath += username; mkpath += "/eosxd/mksquash/"; if (mounttarget.beginswith(mkpath.c_str())) { if (stat(linktarget, &buf)) { fprintf(stderr, "error: cannot find local package directory '%s'\n", linktarget); global_retc = EINVAL; return (0); } fprintf(stderr, "info: squashfs image is currently unpacked/open for local RW mode - use 'eos squash pack %s' to close image\n", packagepath.GetPath()); } else { fprintf(stderr, "info: squashfs image is currently packed - use 'eos squash unpack %s' to open image locally\n", packagepath.GetPath()); } } if (cmd == "rm") { ok = true; eos::common::Path packagepath(path.c_str()); std::string squashpack = packagepath.GetParentPath(); squashpack += "."; squashpack += packagepath.GetName(); squashpack += ".sqsh"; struct stat buf; if (!stat(squashpack.c_str(), &buf)) { if (unlink(squashpack.c_str())) { fprintf(stderr, "error: failed to remove existing squashfs archive '%s' - errno '%d'\n", squashpack.c_str(), errno); global_retc = errno; return (0); } else { fprintf(stderr, "info: removed squashfs image '%s'\n", squashpack.c_str()); } } if (!lstat(packagepath.GetPath(), &buf)) { if (unlink(packagepath.GetPath())) { fprintf(stderr, "error: failed to unlink locally staged squashfs archive '%s' - errno '%d'\n", squashpack.c_str(), errno); global_retc = errno; return (0); } else { fprintf(stderr, "info: removed squashfs smart link '%s\n", packagepath.GetPath()); } } } if (cmd == "rm-release") { std::string scmd = "info-release "; scmd += path.c_str(); com_squash((char*) scmd.c_str()); if (!global_retc) { fprintf(stderr, "info: wiping squashfs releases under '%s'\n", path.c_str()); eos::common::Path packagepath(path.c_str()); std::string nextrelease = std::string(packagepath.GetPath()) + std::string("/next"); std::string currentrelease = std::string(packagepath.GetPath()) + std::string("/current"); std::string archive = std::string(packagepath.GetPath()) + std::string("/.archive"); fprintf(stdout, "info: wiping links current,next ... \n"); ::unlink(currentrelease.c_str()); ::unlink(nextrelease.c_str()); if (archive.substr(0, 5) == "/eos/") { fprintf(stdout, "info: wiping archive ...\n"); scmd = "eos rm -rf "; scmd += archive; std::string out = eos::common::StringConversion::StringFromShellCmd( scmd.c_str()); fprintf(stdout, "%s", out.c_str()); } for (size_t i = 0; i < 50; ++i) { struct stat buf; if (!::stat(archive.c_str(), &buf)) { // we might have to wait for callback notification on the fuse mount that the archive directory was wiped std::this_thread::sleep_for(std::chrono::milliseconds(100)); } else { break; } if (i == 49) { { fprintf(stderr, "=====================================\n"); fprintf(stderr, "warning: mount didn't see cleanup ...\n"); fprintf(stderr, "remote:\n"); fprintf(stderr, "=====================================\n"); scmd = "eos ls -la "; scmd += packagepath.GetPath(); std::string out = eos::common::StringConversion::StringFromShellCmd( scmd.c_str()); fprintf(stdout, "%s", out.c_str()); } { fprintf(stderr, "=====================================\n"); fprintf(stderr, "local:\n"); fprintf(stderr, "=====================================\n"); scmd = "ls -la "; scmd += packagepath.GetPath(); std::string out = eos::common::StringConversion::StringFromShellCmd( scmd.c_str()); fprintf(stdout, "%s", out.c_str()); fprintf(stderr, "=====================================\n"); } } } if (::rmdir(packagepath.GetPath())) { global_retc = errno; fprintf(stderr, "error: failed to clean squashfs release under '%s' error=%d\n", path.c_str(), global_retc); } return (0); } else { fprintf(stderr, "info: there is no squashfs release under '%s'\n", path.c_str()); return (0); } } if (cmd == "new-release") { eos::common::Path packagepath(path.c_str()); XrdOucString version = subtokenizer.GetToken(); std::string packagename = packagepath.GetName(); std::string now = eos::common::StringConversion::StringFromShellCmd("date '+%Y%m%d%H%M%S'"); if (version.length()) { // overwrite with the given version number now = version.c_str(); } now.erase(now.find_last_not_of(" \n\r\t") + 1); std::string archivepath = std::string(packagepath.GetPath()) + std::string("/") + std::string(".archive/"); std::string archivepackage = archivepath + packagename + std::string("-") + now; std::string nextrelease = std::string(packagepath.GetPath()) + std::string("/next"); eos::common::Path archpath(archivepackage.c_str()); if (!archpath.MakeParentPath(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) { fprintf(stderr, "error: couldn't create '%s'\n", archpath.GetParentPath()); global_retc = errno; return (0); } // in the unlikely case this command was executed within 1 second twice ... ::unlink(archivepackage.c_str()); ::unlink(nextrelease.c_str()); std::string scmd = "new \""; scmd += archivepackage; scmd += "\""; int rc = com_squash((char*)scmd.c_str()); if (!rc) { rc = ::symlink(archivepackage.c_str(), nextrelease.c_str()); if (rc) { fprintf(stderr, "error: failed to create symbolic link for next release '%s'\n", nextrelease.c_str()); global_retc = errno; return (0); } } else { fprintf(stderr, "error: failed to create squash package for a new release\n"); return (0); } fprintf(stderr, "info: install the new release under '%s'\n", nextrelease.c_str()); return (0); } if (cmd == "pack-release") { char lname[4096]; memset(lname, 0, sizeof(lname)); eos::common::Path packagepath(path.c_str()); std::string nextrelease = std::string(packagepath.GetPath()) + std::string("/next"); std::string currentrelease = std::string(packagepath.GetPath()) + std::string("/current"); std::string hiddencurrentrelease = std::string(packagepath.GetPath()) + std::string("/.current"); if (::readlink(nextrelease.c_str(), lname, sizeof(lname)) < 0) { fprintf(stderr, "error: failed to find an open release package under '%s'\n", nextrelease.c_str()); global_retc = errno; return (0); } else { std::string scmd = "pack "; scmd += "\""; scmd += lname; scmd += "\""; int rc = com_squash((char*)scmd.c_str()); if (!rc) { rc = ::unlink(nextrelease.c_str()); if (rc) { fprintf(stderr, "error: failed to unlink open release package under '%s'\n", nextrelease.c_str()); global_retc = errno; return (0); } rc = ::symlink(lname, hiddencurrentrelease.c_str()); if (rc) { fprintf(stderr, "error: failed to symlink current release package under '%s'\n", hiddencurrentrelease.c_str()); global_retc = errno; return (0); } else { rc = ::rename(hiddencurrentrelease.c_str(), currentrelease.c_str()); if (rc) { fprintf(stderr, "error: failed to move '%s' to '%s'\n", hiddencurrentrelease.c_str(), currentrelease.c_str()); global_retc = errno; return (0); } else { fprintf(stdout, "info: new release available under '%s'\n", currentrelease.c_str()); return (0); } } } else { fprintf(stderr, "error: failed to pack squash package for a new release\n"); return (0); } } } if (cmd == "info-release") { std::string scmd = "trim-release \""; scmd += path.c_str(); scmd += "\" "; scmd += "999999 999999"; com_squash((char*)scmd.c_str()); return (0); } if (cmd == "trim-release") { eos::common::Path packagepath(path.c_str()); XrdOucString keepdays = subtokenizer.GetToken(); XrdOucString keepversions = subtokenizer.GetToken(); std::string current = packagepath.GetPath(); std::string archive = packagepath.GetPath(); current += "/current"; archive += "/.archive"; struct stat buf; if (::lstat(current.c_str(), &buf)) { fprintf(stderr, "error: I cannot find any current release under '%s'\n", current.c_str()); global_retc = EINVAL; return (0); } if (::lstat(archive.c_str(), &buf)) { fprintf(stderr, "error: I cannot find any archive release under '%s'\n", archive.c_str()); global_retc = EINVAL; return (0); } if (!keepdays.length()) { fprintf(stderr, "error: you have to specify the number of days you want to keep releases : squash trim-release [ \n"); global_retc = EINVAL; return (0); } size_t n_keepversions = keepversions.length() ? strtol(keepversions.c_str(), 0, 10) : 0; if (!n_keepversions) { fprintf(stderr, "info: no !=0 version limit specified ...\n"); keepversions = "1000000"; } else { // we have to pass keepversions + 1 to the find commands keepversions = std::to_string(n_keepversions + 1).c_str(); } std::string find1, find2, find3, find4, find5; find1 = find2 = find3 = find4 = find5 = "find "; find1 += packagepath.GetPath(); find2 += packagepath.GetPath(); find3 += packagepath.GetPath(); find4 += packagepath.GetPath(); find5 += packagepath.GetPath(); find1 += " -type f -mtime +"; find2 += " -type l -mtime +"; find1 += keepdays.c_str(); find2 += keepdays.c_str(); find1 += " -delete"; find2 += " -delete"; find3 += "/.archive/ -type f -printf '%Ts\t%h/%f\n' | sort -rn | tail -n +"; find4 += "/.archive/ -type l -printf '%Ts\t%h/%f\n' | sort -rn | tail -n +"; find3 += keepversions.c_str(); find4 += keepversions.c_str(); find3 += " | cut -f2- | xargs -r rm"; find4 += " | cut -f2- | xargs -r rm"; find5 += " -type l"; eos::common::StringConversion::StringFromShellCmd(find1.c_str()); eos::common::StringConversion::StringFromShellCmd(find2.c_str()); eos::common::StringConversion::StringFromShellCmd(find3.c_str()); eos::common::StringConversion::StringFromShellCmd(find4.c_str()); std::string out = eos::common::StringConversion::StringFromShellCmd( find5.c_str()); fprintf(stdout, "---------------------------------------------------------------------------\n"); fprintf(stdout, "- releases of '%s' \n", packagepath.GetPath()); fprintf(stdout, "---------------------------------------------------------------------------\n"); fprintf(stdout, "%s", out.c_str()); fprintf(stdout, "---------------------------------------------------------------------------\n"); return (0); } if (!ok) { goto com_squash_usage; } return (0); com_squash_usage: fprintf(stdout, "usage: squash new : create a new squashfs under \n"); fprintf(stdout, "\n"); fprintf(stdout, " squash pack [-f] : pack a squashfs image\n"); fprintf(stdout, " -f will recreate the package but keeps the symbolic link locally\n"); fprintf(stdout, "\n"); fprintf(stdout, " squash unpack [-f] : unpack a squashfs image for modification\n"); fprintf(stdout, " -f will atomically update the local package\n"); fprintf(stdout, "\n"); fprintf(stdout, " squash info : squashfs information about \n"); fprintf(stdout, "\n"); fprintf(stdout, " squash rm : delete a squashfs attached image and its smart link\n"); fprintf(stdout, "\n"); fprintf(stdout, " squash relabel : relable a squashfs image link e.g. after an image move in the namespace\n"); fprintf(stdout, "\n"); /* fprintf(stdout, " squash roll : will create a squash package from the EOS directory pointed by : will store the squash package contents unpacked into the EOS package directory\n"); fprintf(stdout,"\n"); */ fprintf(stdout, " squash install --curl=https://.tgz|.tar.gz : create a squashfs package from a web archive under \n"); fprintf(stdout, " squash new-release [] : create a new squashfs release under - by default versions are made from timestamp, but this can be overwritten using the version field\n"); fprintf(stdout, " squash pack-release : pack a squashfs release under \n"); fprintf(stdout, " squash info-release : show all release revisions under \n"); fprintf(stdout, " squash trim-release [] : trim releases older than and keep maximum of release\n"); fprintf(stdout, " squash rm-release : delete all squahfs releases udner \n"); global_retc = EINVAL; return (0); }