Loading commands/system/dmesg.cpp 0 → 100644 +318 −0 Changes for commands/system/dmesg.cpp: 318 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // dmesg: prints the kernel log. Reads /dev/kmsg, whose records carry the // priority and the timestamp separately ("prio,seq,usec,flags;message", // see Documentation/ABI/testing/dev-kmsg); falls back to the older // syslog(2) buffer (klogctl) where /dev/kmsg isn't available. Options as // in util-linux: -c/-C clear, -n console level, -l level filter, -r raw, // -t no timestamps, -T wall-clock timestamps, -w follow. #include <iostream> #include <string> #include <vector> #include <cstdio> #include <cstring> #include <cstdlib> #include <cerrno> #include <ctime> #include <cctype> #include <unistd.h> #include <fcntl.h> #include <sys/klog.h> #include <sys/sysinfo.h> #include "shell.h" namespace fresh { namespace cmd { class DmesgCommand : public ShellCommand { public: DmesgCommand():ShellCommand("dmesg"){} private: enum class Stamp { Seconds, None, Wallclock }; struct Options { bool raw=false; Stamp stamp=Stamp::Seconds; unsigned levelMask=0xff; // bit n: print level n time_t bootTime=0; // for -T }; static const char *levelName(int level){ static const char *names[]={"emerg","alert","crit","err","warn","notice","info","debug"}; return (level>=0 && level<8) ? names[level] : "?"; } static int levelFromName(const std::string &s){ static const char *names[]={"emerg","alert","crit","err","warn","notice","info","debug"}; for(int i=0;i<8;++i) if(s==names[i]) return i; if(s=="warning") return 4; if(s=="error") return 3; if(s.size()==1 && s[0]>='0' && s[0]<='7') return s[0]-'0'; return -1; } // /dev/kmsg escapes unprintable bytes as \xHH static std::string unescape(const std::string &s){ std::string out; for(size_t i=0;i<s.size();++i){ if(s[i]=='\\' && i+3<s.size() && s[i+1]=='x' && isxdigit((unsigned char)s[i+2]) && isxdigit((unsigned char)s[i+3])){ char hex[3]={s[i+2],s[i+3],0}; out+=(char)std::strtol(hex,nullptr,16); i+=3; continue; } out+=s[i]; } return out; } static void print(const Options &o,int pri,unsigned long long usec,const std::string &msg){ int level=pri&7; if(!(o.levelMask&(1u<<level))) return; std::string line; char buf[64]; if(o.raw){ snprintf(buf,sizeof(buf),"<%d>",pri); line+=buf; } if(o.stamp==Stamp::Seconds){ snprintf(buf,sizeof(buf),"[%5llu.%06llu] ",usec/1000000,usec%1000000); line+=buf; }else if(o.stamp==Stamp::Wallclock){ time_t t=o.bootTime+(time_t)(usec/1000000); struct tm tmv; localtime_r(&t,&tmv); strftime(buf,sizeof(buf),"[%a %b %e %H:%M:%S %Y] ",&tmv); line+=buf; } line+=msg; std::cout << line << '\n'; } // One /dev/kmsg record: "prio,seq,usec,flags[,...];message\n" // optionally followed by " KEY=value" continuation lines. static bool parseRecord(const std::string &rec,int &pri,unsigned long long &usec,std::string &msg){ size_t semi=rec.find(';'); if(semi==std::string::npos) return false; std::string head=rec.substr(0,semi); std::vector<std::string> f; size_t p=0; while(true){ size_t c=head.find(',',p); f.push_back(head.substr(p,c==std::string::npos?std::string::npos:c-p)); if(c==std::string::npos) break; p=c+1; } if(f.size()<3) return false; pri=(int)std::strtol(f[0].c_str(),nullptr,10); usec=std::strtoull(f[2].c_str(),nullptr,10); std::string body=rec.substr(semi+1); size_t nl=body.find('\n'); msg=unescape(body.substr(0,nl)); return true; } // Returns 0, or -1 if /dev/kmsg can't be used (caller falls back). int readKmsg(const Options &o,bool follow){ int fd=open("/dev/kmsg",O_RDONLY|O_NONBLOCK|O_CLOEXEC); if(fd<0){ if(errno==EPERM || errno==EACCES){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return -1; } char buf[8192]; bool blocking=false; for(;;){ ssize_t n=read(fd,buf,sizeof(buf)-1); if(n<0){ if(errno==EINTR) continue; if(errno==EPIPE) continue; // overwritten while reading: skip if(errno==EAGAIN && follow && !blocking){ std::cout.flush(); int fl=fcntl(fd,F_GETFL); fcntl(fd,F_SETFL,fl&~O_NONBLOCK); blocking=true; continue; } if(errno==EAGAIN) break; // everything read if(errno==EPERM || errno==EACCES){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; close(fd); return 1; } break; } if(n==0) break; int pri; unsigned long long usec; std::string msg; if(parseRecord(std::string(buf,(size_t)n),pri,usec,msg)) print(o,pri,usec,msg); if(blocking) std::cout.flush(); } close(fd); return 0; } // syslog(2) buffer: lines "<prio>[ seconds.micros] message" int readKlog(const Options &o){ int size=klogctl(SYSLOG_ACTION_SIZE_BUFFER,nullptr,0); if(size<=0) size=1<<17; std::vector<char> buf((size_t)size+1); int n=klogctl(SYSLOG_ACTION_READ_ALL,buf.data(),size); if(n<0){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; return 1; } std::string all(buf.data(),(size_t)n); size_t p=0; while(p<all.size()){ size_t nl=all.find('\n',p); std::string line=all.substr(p,nl==std::string::npos?std::string::npos:nl-p); p=nl==std::string::npos?all.size():nl+1; int pri=6; unsigned long long usec=0; if(line.size()>2 && line[0]=='<'){ size_t gt=line.find('>'); if(gt!=std::string::npos){ pri=(int)std::strtol(line.c_str()+1,nullptr,10); line=line.substr(gt+1); } } if(!line.empty() && line[0]=='['){ size_t rb=line.find(']'); if(rb!=std::string::npos){ double secs=std::strtod(line.c_str()+1,nullptr); usec=(unsigned long long)(secs*1e6+0.5); line=line.substr(rb+1); if(!line.empty() && line[0]==' ') line.erase(0,1); } } print(o,pri,usec,line); } return 0; } static void usage(){ std::cerr << "usage: dmesg [-c|-C] [-n level] [-l levels] [-r] [-t|-T] [-w]\n" << " -c print the buffer, then clear it\n" << " -C clear the buffer\n" << " -n level set the console log level (1-8 or a name)\n" << " -l levels only these levels, comma separated (emerg..debug)\n" << " -r raw: keep the <priority> prefix\n" << " -t no timestamps\n" << " -T wall-clock timestamps\n" << " -w wait for and print new messages\n"; } public: int runCommand(int argc,const char **argv){ optind=0; Options o; bool clearAfter=false,clearOnly=false,follow=false; int consoleLevel=-1; int opt; while((opt=getopt(argc,const_cast<char *const *>(argv),"cCn:l:rtTwh"))!=-1){ switch(opt){ case 'c': clearAfter=true; break; case 'C': clearOnly=true; break; case 'n': { consoleLevel=levelFromName(optarg); if(consoleLevel>=0 && std::string(optarg).size()>1) consoleLevel+=1; // names: level+1, as util-linux else consoleLevel=atoi(optarg); if(consoleLevel<1 || consoleLevel>8){ std::cerr << "dmesg: invalid console level: " << optarg << std::endl; return 1; } break; } case 'l': { o.levelMask=0; std::string list=optarg; size_t p=0; while(p<=list.size()){ size_t c=list.find(',',p); std::string name=list.substr(p,c==std::string::npos?std::string::npos:c-p); int lv=levelFromName(name); if(lv<0){ std::cerr << "dmesg: unknown level '" << name << "'" << std::endl; return 1; } o.levelMask|=1u<<lv; if(c==std::string::npos) break; p=c+1; } break; } case 'r': o.raw=true; break; case 't': o.stamp=Stamp::None; break; case 'T': o.stamp=Stamp::Wallclock; break; case 'w': follow=true; break; default: usage(); return opt=='h' ? 0 : 1; } } if(consoleLevel>0){ if(klogctl(SYSLOG_ACTION_CONSOLE_LEVEL,nullptr,consoleLevel)<0){ std::cerr << "dmesg: setting the console level failed: " << strerror(errno) << std::endl; return 1; } return 0; } if(clearOnly){ if(klogctl(SYSLOG_ACTION_CLEAR,nullptr,0)<0){ std::cerr << "dmesg: clearing the kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return 0; } if(o.stamp==Stamp::Wallclock){ struct sysinfo si; if(sysinfo(&si)==0) o.bootTime=time(nullptr)-si.uptime; } int rc=readKmsg(o,follow); if(rc<0) rc=readKlog(o); std::cout.flush(); if(rc==0 && clearAfter && klogctl(SYSLOG_ACTION_CLEAR,nullptr,0)<0){ std::cerr << "dmesg: clearing the kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return rc; } }; FRESH_REGISTER_COMMAND(DmesgCommand); }} Loading
commands/system/dmesg.cpp 0 → 100644 +318 −0 Changes for commands/system/dmesg.cpp: 318 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2026, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ // dmesg: prints the kernel log. Reads /dev/kmsg, whose records carry the // priority and the timestamp separately ("prio,seq,usec,flags;message", // see Documentation/ABI/testing/dev-kmsg); falls back to the older // syslog(2) buffer (klogctl) where /dev/kmsg isn't available. Options as // in util-linux: -c/-C clear, -n console level, -l level filter, -r raw, // -t no timestamps, -T wall-clock timestamps, -w follow. #include <iostream> #include <string> #include <vector> #include <cstdio> #include <cstring> #include <cstdlib> #include <cerrno> #include <ctime> #include <cctype> #include <unistd.h> #include <fcntl.h> #include <sys/klog.h> #include <sys/sysinfo.h> #include "shell.h" namespace fresh { namespace cmd { class DmesgCommand : public ShellCommand { public: DmesgCommand():ShellCommand("dmesg"){} private: enum class Stamp { Seconds, None, Wallclock }; struct Options { bool raw=false; Stamp stamp=Stamp::Seconds; unsigned levelMask=0xff; // bit n: print level n time_t bootTime=0; // for -T }; static const char *levelName(int level){ static const char *names[]={"emerg","alert","crit","err","warn","notice","info","debug"}; return (level>=0 && level<8) ? names[level] : "?"; } static int levelFromName(const std::string &s){ static const char *names[]={"emerg","alert","crit","err","warn","notice","info","debug"}; for(int i=0;i<8;++i) if(s==names[i]) return i; if(s=="warning") return 4; if(s=="error") return 3; if(s.size()==1 && s[0]>='0' && s[0]<='7') return s[0]-'0'; return -1; } // /dev/kmsg escapes unprintable bytes as \xHH static std::string unescape(const std::string &s){ std::string out; for(size_t i=0;i<s.size();++i){ if(s[i]=='\\' && i+3<s.size() && s[i+1]=='x' && isxdigit((unsigned char)s[i+2]) && isxdigit((unsigned char)s[i+3])){ char hex[3]={s[i+2],s[i+3],0}; out+=(char)std::strtol(hex,nullptr,16); i+=3; continue; } out+=s[i]; } return out; } static void print(const Options &o,int pri,unsigned long long usec,const std::string &msg){ int level=pri&7; if(!(o.levelMask&(1u<<level))) return; std::string line; char buf[64]; if(o.raw){ snprintf(buf,sizeof(buf),"<%d>",pri); line+=buf; } if(o.stamp==Stamp::Seconds){ snprintf(buf,sizeof(buf),"[%5llu.%06llu] ",usec/1000000,usec%1000000); line+=buf; }else if(o.stamp==Stamp::Wallclock){ time_t t=o.bootTime+(time_t)(usec/1000000); struct tm tmv; localtime_r(&t,&tmv); strftime(buf,sizeof(buf),"[%a %b %e %H:%M:%S %Y] ",&tmv); line+=buf; } line+=msg; std::cout << line << '\n'; } // One /dev/kmsg record: "prio,seq,usec,flags[,...];message\n" // optionally followed by " KEY=value" continuation lines. static bool parseRecord(const std::string &rec,int &pri,unsigned long long &usec,std::string &msg){ size_t semi=rec.find(';'); if(semi==std::string::npos) return false; std::string head=rec.substr(0,semi); std::vector<std::string> f; size_t p=0; while(true){ size_t c=head.find(',',p); f.push_back(head.substr(p,c==std::string::npos?std::string::npos:c-p)); if(c==std::string::npos) break; p=c+1; } if(f.size()<3) return false; pri=(int)std::strtol(f[0].c_str(),nullptr,10); usec=std::strtoull(f[2].c_str(),nullptr,10); std::string body=rec.substr(semi+1); size_t nl=body.find('\n'); msg=unescape(body.substr(0,nl)); return true; } // Returns 0, or -1 if /dev/kmsg can't be used (caller falls back). int readKmsg(const Options &o,bool follow){ int fd=open("/dev/kmsg",O_RDONLY|O_NONBLOCK|O_CLOEXEC); if(fd<0){ if(errno==EPERM || errno==EACCES){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return -1; } char buf[8192]; bool blocking=false; for(;;){ ssize_t n=read(fd,buf,sizeof(buf)-1); if(n<0){ if(errno==EINTR) continue; if(errno==EPIPE) continue; // overwritten while reading: skip if(errno==EAGAIN && follow && !blocking){ std::cout.flush(); int fl=fcntl(fd,F_GETFL); fcntl(fd,F_SETFL,fl&~O_NONBLOCK); blocking=true; continue; } if(errno==EAGAIN) break; // everything read if(errno==EPERM || errno==EACCES){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; close(fd); return 1; } break; } if(n==0) break; int pri; unsigned long long usec; std::string msg; if(parseRecord(std::string(buf,(size_t)n),pri,usec,msg)) print(o,pri,usec,msg); if(blocking) std::cout.flush(); } close(fd); return 0; } // syslog(2) buffer: lines "<prio>[ seconds.micros] message" int readKlog(const Options &o){ int size=klogctl(SYSLOG_ACTION_SIZE_BUFFER,nullptr,0); if(size<=0) size=1<<17; std::vector<char> buf((size_t)size+1); int n=klogctl(SYSLOG_ACTION_READ_ALL,buf.data(),size); if(n<0){ std::cerr << "dmesg: read kernel buffer failed: " << strerror(errno) << std::endl; return 1; } std::string all(buf.data(),(size_t)n); size_t p=0; while(p<all.size()){ size_t nl=all.find('\n',p); std::string line=all.substr(p,nl==std::string::npos?std::string::npos:nl-p); p=nl==std::string::npos?all.size():nl+1; int pri=6; unsigned long long usec=0; if(line.size()>2 && line[0]=='<'){ size_t gt=line.find('>'); if(gt!=std::string::npos){ pri=(int)std::strtol(line.c_str()+1,nullptr,10); line=line.substr(gt+1); } } if(!line.empty() && line[0]=='['){ size_t rb=line.find(']'); if(rb!=std::string::npos){ double secs=std::strtod(line.c_str()+1,nullptr); usec=(unsigned long long)(secs*1e6+0.5); line=line.substr(rb+1); if(!line.empty() && line[0]==' ') line.erase(0,1); } } print(o,pri,usec,line); } return 0; } static void usage(){ std::cerr << "usage: dmesg [-c|-C] [-n level] [-l levels] [-r] [-t|-T] [-w]\n" << " -c print the buffer, then clear it\n" << " -C clear the buffer\n" << " -n level set the console log level (1-8 or a name)\n" << " -l levels only these levels, comma separated (emerg..debug)\n" << " -r raw: keep the <priority> prefix\n" << " -t no timestamps\n" << " -T wall-clock timestamps\n" << " -w wait for and print new messages\n"; } public: int runCommand(int argc,const char **argv){ optind=0; Options o; bool clearAfter=false,clearOnly=false,follow=false; int consoleLevel=-1; int opt; while((opt=getopt(argc,const_cast<char *const *>(argv),"cCn:l:rtTwh"))!=-1){ switch(opt){ case 'c': clearAfter=true; break; case 'C': clearOnly=true; break; case 'n': { consoleLevel=levelFromName(optarg); if(consoleLevel>=0 && std::string(optarg).size()>1) consoleLevel+=1; // names: level+1, as util-linux else consoleLevel=atoi(optarg); if(consoleLevel<1 || consoleLevel>8){ std::cerr << "dmesg: invalid console level: " << optarg << std::endl; return 1; } break; } case 'l': { o.levelMask=0; std::string list=optarg; size_t p=0; while(p<=list.size()){ size_t c=list.find(',',p); std::string name=list.substr(p,c==std::string::npos?std::string::npos:c-p); int lv=levelFromName(name); if(lv<0){ std::cerr << "dmesg: unknown level '" << name << "'" << std::endl; return 1; } o.levelMask|=1u<<lv; if(c==std::string::npos) break; p=c+1; } break; } case 'r': o.raw=true; break; case 't': o.stamp=Stamp::None; break; case 'T': o.stamp=Stamp::Wallclock; break; case 'w': follow=true; break; default: usage(); return opt=='h' ? 0 : 1; } } if(consoleLevel>0){ if(klogctl(SYSLOG_ACTION_CONSOLE_LEVEL,nullptr,consoleLevel)<0){ std::cerr << "dmesg: setting the console level failed: " << strerror(errno) << std::endl; return 1; } return 0; } if(clearOnly){ if(klogctl(SYSLOG_ACTION_CLEAR,nullptr,0)<0){ std::cerr << "dmesg: clearing the kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return 0; } if(o.stamp==Stamp::Wallclock){ struct sysinfo si; if(sysinfo(&si)==0) o.bootTime=time(nullptr)-si.uptime; } int rc=readKmsg(o,follow); if(rc<0) rc=readKlog(o); std::cout.flush(); if(rc==0 && clearAfter && klogctl(SYSLOG_ACTION_CLEAR,nullptr,0)<0){ std::cerr << "dmesg: clearing the kernel buffer failed: " << strerror(errno) << std::endl; return 1; } return rc; } }; FRESH_REGISTER_COMMAND(DmesgCommand); }}