2000th COMMIT: userspace: Implement basic fetch program bananfetch
This patch adds a obligatory fetch program to banan-os! This program (`bananfetch`) fetches some basic information about the operating system and the hardware its running on!
This commit is contained in:
		
							parent
							
								
									44f0ec601f
								
							
						
					
					
						commit
						91d513a672
					
				|  | @ -1,4 +1,5 @@ | |||
| set(USERSPACE_PROGRAMS | ||||
| 	bananfetch | ||||
| 	cat | ||||
| 	cat-mmap | ||||
| 	chmod | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| set(SOURCES | ||||
| 	main.cpp | ||||
| ) | ||||
| 
 | ||||
| add_executable(bananfetch ${SOURCES}) | ||||
| banan_link_library(bananfetch ban) | ||||
| banan_link_library(bananfetch libc) | ||||
| 
 | ||||
| install(TARGETS bananfetch OPTIONAL) | ||||
|  | @ -0,0 +1,281 @@ | |||
| #include <BAN/IPv4.h> | ||||
| #include <BAN/String.h> | ||||
| #include <BAN/Vector.h> | ||||
| 
 | ||||
| #include <arpa/inet.h> | ||||
| #include <dirent.h> | ||||
| #include <fcntl.h> | ||||
| #include <limits.h> | ||||
| #include <net/if.h> | ||||
| #include <netinet/in.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <stropts.h> | ||||
| #include <sys/banan-os.h> | ||||
| #include <sys/framebuffer.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/utsname.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #define COLOR_ON "\e[33m" | ||||
| #define COLOR_OFF "\e[m" | ||||
| 
 | ||||
| static constexpr const char* s_banana_art1[] { | ||||
| 	"                X. ", | ||||
| 	"               :;  ", | ||||
| 	"              .:;  ", | ||||
| 	"              ::::.", | ||||
| 	"            .::::;.", | ||||
| 	".;.      ..::::;;; ", | ||||
| 	";:::;::::::::;;;.  ", | ||||
| 	" x;;;;;;;;;;;;.    ", | ||||
| 	"    ..x+;..        " | ||||
| }; | ||||
| static constexpr size_t s_banana_art1_width = 19; | ||||
| static constexpr size_t s_banana_art1_height = 9; | ||||
| 
 | ||||
| static constexpr const char* s_banana_art2[] { | ||||
| 	"                Z¨ ", | ||||
| 	"               )r  ", | ||||
| 	"              `•  ", | ||||
| 	"              «/¿7`", | ||||
| 	"            `»!}[ì`", | ||||
| 	"”<`      ``«•×}ï1= ", | ||||
| 	">**¿<+)//(†×¿vîr´  ", | ||||
| 	" U==íï<<ííîcoc´    ", | ||||
| 	"    ´¨kC‰·`        " | ||||
| }; | ||||
| static constexpr size_t s_banana_art2_width = 19; | ||||
| static constexpr size_t s_banana_art2_height = 9; | ||||
| 
 | ||||
| const char* get_cpu_manufacturer() | ||||
| { | ||||
| 	uint32_t max_extended; | ||||
| 	asm volatile("cpuid" : "=a"(max_extended) : "a"(0x80000000)); | ||||
| 	if (max_extended >= 0x80000004) | ||||
| 	{ | ||||
| 		static char string[49]; | ||||
| 		for (int i = 0; i < 3; i++) | ||||
| 		{ | ||||
| 			uint32_t eax, ebx, ecx, edx; | ||||
| 			asm volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0x80000002 + i)); | ||||
| 			memcpy(string + i * 16 +  0, &eax, 4); | ||||
| 			memcpy(string + i * 16 +  4, &ebx, 4); | ||||
| 			memcpy(string + i * 16 +  8, &ecx, 4); | ||||
| 			memcpy(string + i * 16 + 12, &edx, 4); | ||||
| 		} | ||||
| 		string[48] = '\0'; | ||||
| 		return string; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		uint32_t ebx, ecx, edx; | ||||
| 		asm volatile("cpuid" : "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(0)); | ||||
| 
 | ||||
| 		char string[13]; | ||||
| 		memcpy(string + 0, &ebx, 4); | ||||
| 		memcpy(string + 4, &edx, 4); | ||||
| 		memcpy(string + 8, &ecx, 4); | ||||
| 		string[12] = '\0'; | ||||
| 
 | ||||
| 		if (strcmp(string, "AuthenticAMD") == 0) return "AMD"; | ||||
| 		if (strcmp(string, "CentaurHauls") == 0) return "Centaur"; | ||||
| 		if (strcmp(string, "CyrixInstead") == 0) return "Cyrix"; | ||||
| 		if (strcmp(string, "GenuineIntel") == 0) return "Intel"; | ||||
| 		if (strcmp(string, "GenuineIotel") == 0) return "Intel"; | ||||
| 		if (strcmp(string, "TransmetaCPU") == 0) return "Transmeta"; | ||||
| 		if (strcmp(string, "GenuineTMx86") == 0) return "Transmeta"; | ||||
| 		if (strcmp(string, "Geode by NSC") == 0) return "National Semiconductor"; | ||||
| 		if (strcmp(string, "NexGenDriven") == 0) return "NexGen"; | ||||
| 		if (strcmp(string, "RiseRiseRise") == 0) return "Rise"; | ||||
| 		if (strcmp(string, "SiS SiS SiS ") == 0) return "Sis"; | ||||
| 		if (strcmp(string, "UMC UMC UMC ") == 0) return "UMC"; | ||||
| 		if (strcmp(string, "Vortex86 SoC") == 0) return "Vortex86"; | ||||
| 		if (strcmp(string, "  Shanghai  ") == 0) return "Zhaoxin"; | ||||
| 		if (strcmp(string, "HygonGenuine") == 0) return "Hygon"; | ||||
| 		if (strcmp(string, "Genuine  RDC") == 0) return "RDC Semiconductor"; | ||||
| 		if (strcmp(string, "E2K MACHINE ") == 0) return "MCST Elbrus"; | ||||
| 		if (strcmp(string, "VIA VIA VIA ") == 0) return "VIA"; | ||||
| 		if (strcmp(string, "AMD ISBETTER") == 0) return "AMD"; | ||||
| 
 | ||||
| 		if (strcmp(string, "GenuineAO486") == 0) return "ao486"; | ||||
| 		if (strcmp(string, "MiSTer AO486") == 0) return "ao486"; | ||||
| 
 | ||||
| 		if (strcmp(string, "MicrosoftXTA") == 0) return "Microsoft x86-to-ARM"; | ||||
| 		if (strcmp(string, "VirtualApple") == 0) return "Apple Rosetta 2"; | ||||
| 
 | ||||
| 		return "<unknown>"; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| BAN::ErrorOr<BAN::Vector<BAN::String>> get_info_lines() | ||||
| { | ||||
| 	BAN::Vector<BAN::String> info_lines; | ||||
| 
 | ||||
| 	struct utsname utsname; | ||||
| 	if (uname(&utsname) == -1) | ||||
| 	{ | ||||
| 		perror("uname"); | ||||
| 		return BAN::Error::from_errno(errno); | ||||
| 	} | ||||
| 
 | ||||
| 	char hostname[HOST_NAME_MAX]; | ||||
| 	if (gethostname(hostname, sizeof(hostname)) == -1) | ||||
| 	{ | ||||
| 		perror("gethostname"); | ||||
| 		return BAN::Error::from_errno(errno); | ||||
| 	} | ||||
| 
 | ||||
| 	const char* login = getlogin(); | ||||
| 	if (login == nullptr) | ||||
| 	{ | ||||
| 		perror("getlogin"); | ||||
| 		return BAN::Error::from_errno(errno); | ||||
| 	} | ||||
| 
 | ||||
| 	timespec uptime; | ||||
| 	if (clock_gettime(CLOCK_MONOTONIC, &uptime) == -1) | ||||
| 	{ | ||||
| 		perror("clock_gettime"); | ||||
| 		return BAN::Error::from_errno(errno); | ||||
| 	} | ||||
| 
 | ||||
| 	TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "{}" COLOR_OFF "@" COLOR_ON "{}", login, hostname)))); | ||||
| 
 | ||||
| 	{ | ||||
| 		const size_t host_length = strlen(login) + strlen(hostname) + 1; | ||||
| 		TRY(info_lines.emplace_back()); | ||||
| 		TRY(info_lines.back().reserve(host_length)); | ||||
| 		for (size_t i = 0; i < host_length; i++) | ||||
| 			MUST(info_lines.back().push_back('-')); | ||||
| 	} | ||||
| 
 | ||||
| 	TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "OS" COLOR_OFF ": {} {}", utsname.sysname, utsname.machine)))); | ||||
| 
 | ||||
| 	TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "Kernel" COLOR_OFF ": {}", utsname.release)))); | ||||
| 
 | ||||
| 	{ | ||||
| 		const uint32_t uptime_day = uptime.tv_sec / (60 * 60 * 24); | ||||
| 		uptime.tv_sec %= 60 * 60 * 24; | ||||
| 
 | ||||
| 		const uint32_t uptime_hour = uptime.tv_sec / (60 * 60); | ||||
| 		uptime.tv_sec %= 60 * 60; | ||||
| 
 | ||||
| 		const uint32_t uptime_minute = uptime.tv_sec / 60; | ||||
| 		uptime.tv_sec %= 60; | ||||
| 
 | ||||
| 		TRY(info_lines.emplace_back(COLOR_ON "Uptime" COLOR_OFF ": "_sv)); | ||||
| 		if (uptime_day) | ||||
| 			TRY(info_lines.back().append(TRY(BAN::String::formatted("{}d ", uptime_day)))); | ||||
| 		if (uptime_hour) | ||||
| 			TRY(info_lines.back().append(TRY(BAN::String::formatted("{}h ", uptime_hour)))); | ||||
| 		TRY(info_lines.back().append(TRY(BAN::String::formatted("{}m ", uptime_minute)))); | ||||
| 	} | ||||
| 
 | ||||
| 	if (DIR* dirp = opendir("/usr/bin"); dirp != nullptr) | ||||
| 	{ | ||||
| 		size_t program_count = 0; | ||||
| 		struct dirent* dirent; | ||||
| 		while ((dirent = readdir(dirp))) | ||||
| 		{ | ||||
| 			if (dirent->d_type != DT_REG) | ||||
| 				continue; | ||||
| 
 | ||||
| 			struct stat st; | ||||
| 			if (fstatat(dirfd(dirp), dirent->d_name, &st, 0) == -1) | ||||
| 				continue; | ||||
| 
 | ||||
| 			program_count += !!(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)); | ||||
| 		} | ||||
| 
 | ||||
| 		closedir(dirp); | ||||
| 
 | ||||
| 		TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "Programs" COLOR_OFF ": {}", program_count)))); | ||||
| 	} | ||||
| 
 | ||||
| 	TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "CPU" COLOR_OFF ": {}", get_cpu_manufacturer())))); | ||||
| 
 | ||||
| 	if (int meminfo_fd = open("/proc/meminfo", O_RDONLY); meminfo_fd != -1) | ||||
| 	{ | ||||
| 		full_meminfo_t meminfo; | ||||
| 		if (read(meminfo_fd, &meminfo, sizeof(meminfo)) == sizeof(meminfo)) | ||||
| 		{ | ||||
| 			const size_t total_bytes = (meminfo.free_pages + meminfo.used_pages) * meminfo.page_size; | ||||
| 			const size_t used_bytes = meminfo.used_pages * meminfo.page_size; | ||||
| 			TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "Memory" COLOR_OFF ": {}MiB / {}MiB", used_bytes >> 20, total_bytes >> 20)))); | ||||
| 		} | ||||
| 		close(meminfo_fd); | ||||
| 	} | ||||
| 
 | ||||
| 	if (int fb_fd = open("/dev/fb0", O_RDONLY); fb_fd != -1) | ||||
| 	{ | ||||
| 		framebuffer_info_t fb_info; | ||||
| 		if (pread(fb_fd, &fb_info, sizeof(fb_info), -1) == sizeof(framebuffer_info_t)) | ||||
| 			TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "Resolution" COLOR_OFF ": {}x{}", fb_info.width, fb_info.height)))); | ||||
| 		close(fb_fd); | ||||
| 	} | ||||
| 
 | ||||
| 	if (int socket = ::socket(AF_INET, SOCK_DGRAM, 0); socket != -1) | ||||
| 	{ | ||||
| 		sockaddr_in sockaddr; | ||||
| 		sockaddr.sin_family = AF_INET; | ||||
| 		sockaddr.sin_port = 0; | ||||
| 		sockaddr.sin_addr.s_addr = INADDR_ANY; | ||||
| 		if (bind(socket, reinterpret_cast<struct sockaddr*>(&sockaddr), sizeof(sockaddr)) == 0) | ||||
| 		{ | ||||
| 			ifreq ifreq; | ||||
| 			if (ioctl(socket, SIOCGIFADDR, &ifreq) == 0) | ||||
| 			{ | ||||
| 				auto& ifru_addr = *reinterpret_cast<sockaddr_in*>(&ifreq.ifr_ifru.ifru_addr); | ||||
| 				if (ifru_addr.sin_family == AF_INET) | ||||
| 					TRY(info_lines.push_back(TRY(BAN::String::formatted(COLOR_ON "Local IPv4" COLOR_OFF ": {}", BAN::IPv4Address(ifru_addr.sin_addr.s_addr))))); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		close(socket); | ||||
| 	} | ||||
| 
 | ||||
| 	TRY(info_lines.emplace_back()); | ||||
| 
 | ||||
| 	TRY(info_lines.emplace_back()); | ||||
| 	for (int color = 40; color <= 47; color++) | ||||
| 		TRY(info_lines.back().append(TRY(BAN::String::formatted("\e[{}m   ", color)))); | ||||
| 
 | ||||
| 	TRY(info_lines.emplace_back()); | ||||
| 	for (int color = 100; color <= 107; color++) | ||||
| 		TRY(info_lines.back().append(TRY(BAN::String::formatted("\e[{}m   ", color)))); | ||||
| 
 | ||||
| 	return info_lines; | ||||
| } | ||||
| 
 | ||||
| int main() | ||||
| { | ||||
| 	constexpr auto& banana_art = s_banana_art2; | ||||
| 	constexpr size_t banana_width = s_banana_art2_width; | ||||
| 	constexpr size_t banana_height = s_banana_art2_height; | ||||
| 
 | ||||
| 	auto info_lines_or_error = get_info_lines(); | ||||
| 	if (info_lines_or_error.is_error()) | ||||
| 	{ | ||||
| 		fprintf(stderr, "Could not get system information: %s\n", info_lines_or_error.error().get_message()); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	auto info_lines = info_lines_or_error.release_value(); | ||||
| 
 | ||||
| 	for (size_t i = 0; i < BAN::Math::max(banana_height, info_lines.size()); i++) | ||||
| 	{ | ||||
| 		if (i < banana_height) | ||||
| 		{ | ||||
| 			printf("\e[1G" COLOR_ON); | ||||
| 			printf("%s", banana_art[i]); | ||||
| 		} | ||||
| 
 | ||||
| 		if (i < info_lines.size()) | ||||
| 		{ | ||||
| 			printf("\e[%zuG" COLOR_OFF, banana_width + 2); | ||||
| 			printf("%s", info_lines[i].data()); | ||||
| 		} | ||||
| 
 | ||||
| 		printf(COLOR_OFF "\n"); | ||||
| 	} | ||||
| } | ||||
		Loading…
	
		Reference in New Issue