uni

University stuff
git clone git://git.christosmarg.xyz/uni-assignments.git
Log | Files | Refs | README | LICENSE

commit 10ef6c622b262361c4dd98306a2dda55416534de
parent 038a2e90c7beb02793240812fd79a8e73acea0fd
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sun, 10 Jan 2021 22:30:40 +0200

new assignments done

Diffstat:
Mc-parallel-computing/ex2/doc.pdf | 0
Mc-parallel-computing/ex2/doc.tex | 228++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mc-parallel-computing/ex2/ex2.c | 129+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Ac-parallel-computing/ex2/res/run1.png | 0
Ac-parallel-computing/ex2/res/run2.png | 0
Ac-parallel-computing/ex2/res/run3.png | 0
Msh-os1/ex2/bck | 21+++++++++++++++++++--
Msh-os1/ex2/createpvs | 33+++++++++++++++++++++++++++++++++
Msh-os1/ex2/mfproc | 44+++++++++++++++++++++++++++++++++++++++++++-
Msh-os1/ex2/searching | 46++++++++++++++++++++++++++++++++--------------
Msh-os1/ex2/teldb | 26++++++++++++++++++++++++++
11 files changed, 459 insertions(+), 68 deletions(-)

diff --git a/c-parallel-computing/ex2/doc.pdf b/c-parallel-computing/ex2/doc.pdf Binary files differ. diff --git a/c-parallel-computing/ex2/doc.tex b/c-parallel-computing/ex2/doc.tex @@ -6,11 +6,19 @@ \usepackage{listings} \usepackage{mathtools} \usepackage{xcolor} -\usepackage[backend=biber]{biblatex} +\usepackage{biblatex} +\usepackage[left=2cm,right=2cm]{geometry} + +\lstset { + basicstyle=\ttfamily, + columns=fullflexible, + breaklines=true, + keepspaces=true +} \title{Εργαστήριο Παράλληλου Υπολογισμού - Εργασία 2} \author{Χρήστος Μαργιώλης} -\date{Δεκέμβριος 2020} +\date{Ιανουάριος 2020} \begin{document} @@ -21,4 +29,220 @@ \renewcommand{\contentsname}{Περιεχόμενα} \tableofcontents +\section{Τεκμηρίωση} + +Το πρόγραμμα, προκειμένου να υλοποίησει τα ερωτήματα της άσκησης, +ακολουθεί την εξής δομή: +\begin{itemize} + \item Δέχεται το διάνυσμα $X$ και το μήκος του στον επεξεργαστή 0. + \item Υπολογίζει τα παρακάτω ωστέ να είναι πιο + εύκολη και οργανωμένη η υλοποίηση του υπόλοιπου + προγράμματος. + \begin{itemize} + \item $X_{min}$ + \item $X_{max}$ + \item $m$ - Μέση τιμή + \item \lstinline{scounts} και \lstinline{displs} - Για να + χρησιμοποιηθούν από τις \lstinline{MPI_Scatterv(3)} + και \lstinline{MPI_Gatherv(3)}. + \item Τοπικό $n$ για τον κάθε επεξεργαστή καθώς και το + διάνυσμα που του αναλογεί. + \end{itemize} + \item Αφού γίνουν επιτυχώς τα παραπάνω, υλοποιεί τα ερωτήματα. + \begin{itemize} + \item Πόσα στοιχεία είναι μικρότερα ή μεγαλύτερα της μέσης τιμής $m$. + \item Την διασπορά των στοιχείων του διανύσματος $X$. + \item Διάνυσμα $Δ$. + \item Την μεγαλύτερη τιμή του διανύσματος $Δ$ καθώς και την + θέση της στο διάνυσμα. + \item Το διάνυσμα προθεμάτων (prefix sums) των στοιχείων + του διανύσματος $X$. + \end{itemize} + \item Εμφανίζει όλα τα ζητούμενα αποτελέσματα στον επεξεργαστή 0. +\end{itemize} + +Αυτό που αξίζει προσοχή είναι οι πίνακες \lstinline{scounts} και +\lstinline{displs} που ανέφερα παραπάνω. Προκειμένου να μοιράζεται σωστά το +διάνυσμα ακόμα και στην περίπτωση που το $n$ δεν είναι ακέραιο πολλαπλάσιο του +$p$, πρέπει να χρησιμοποιηθούνε οι συναρτήσεις \lstinline{MPI_Scatterv(3)} και +\lstinline{MPI_Gatherv(3)} - αυτές οι συναρτήσεις είναι παρόμοιες με τις +\lstinline{MPI_Scatter(3)} και \lstinline{MPI_Gather(3)}, με την διαφορά ότι +μπορούνε να δεχτούνε μεταβλητό αριθμό στοιχείων προς αποστολή στον κάθε επεξεργαστή. +Για παράδειγμα, ας υποθέσουμε ότι είμαστε στην περίπτωση που το $n$ \textit{είναι} +πολλαπλάσιο του $p$ και έχουμε το εξής input: + +\[p = 4\] +\[n = 8 \] +\[X = \{1, 2, 3, 4, 5, 6, 7, 8\}\] +Τότε, μπορούμε να ισομοιράσουμε τα στοιχεία στους $p = 4$ επεξεργαστές ως εξής: +\[p_0 = \{1, 2\}\] +\[p_1 = \{3, 4\}\] +\[p_2 = \{5, 6\}\] +\[p_3 = \{7, 8\}\] + +Στην περίπτωση όμως που το $n$ \textit{δεν} είναι ακέραιο πολλαπλάσιο του $p$, +χρειαζόμαστε να μοιράσουμε τα στοιχεία έτσι ώστε όλοι οι επεξεργαστές να έχουνε +μέχρι 1 στοιχείο παραπάνω, προκειμένου ο υπολογιστικός φόρτος να είναι όσο το +δυνατόν πιο ίσα μοιρασμένος. Οπότε, έχοντας το παρακάτω input ως παράδειγμα: + +\[p = 4\] +\[n = 11 \] +\[X = \{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11\}\] +Με βάση τα παραπάνω λεγόμενά μου, τα στοιχεία πρέπει να μοιραστούν ως εξής: +\[p_0 = \{1, 2, 3\}\] +\[p_1 = \{4, 5, 6\}\] +\[p_2 = \{7, 8, 9\}\] +\[p_3 = \{10, 11\}\] + +Προκειμένου να επιλυθεί αυτό το πρόβλημα, χρησιμοποιούμε, όπως προανέφερα, +τις συναρτήσεις \lstinline{MPI_Scatterv(3)} και \lstinline{MPI_Gatherv(3)}. +Τα νέα ορίσματα που μάς απασχολούνε είναι τα εξής δύο: + +\begin{itemize} + \item \lstinline{scounts} - αριθμός στοιχείων που παίρνει ο κάθε επεξεργαστής + \item \lstinline{displs} - offset στο γενικό διάνυσμα +\end{itemize} + +Και τα δύο ορίσματα (πίνακες) έχουνε μήκος $p$, ωστέ σε κάθε +θέση του πίνακα να υπάρχουνε οι κατάλληλες πληροφορίες για το πώς θα μοιραστούν +τα στοιχεία στον κάθε επεξεργαστή. + +Έπειτα, θέτουμε τις κατάλληλες τιμές για το τοπικό $n$ και διάνυσμα. Στον κώδικα +έχω κάνει \lstinline{localn = scounts[rank]} το οποίο, αν και προφανές, σημαίνει +ότι το κάθε τοπικό διάνυσμα έχει μήκος όσο και τα στοιχεία που υπολογίσαμε ότι +θα έχει ο επεξεργαστής που θα το δεχτεί. Στην συνέχεια στέλνουμε - επιτέλους - +μέσω της \lstinline{MPI_Scatterv(3)} σε όλους τους επεξεργαστές το διάνυσμα που +τούς αναλογεί. + +Μετά από τα παραπάνω βήματα, ξεκινάνε οι υπολογισμοί για τα ζητούμενα της άσκησης, +οπότε θα εξήγησω στο επόμενος μέρος περιληπτικά τί κάνει η κάθε συνάρτηση που έχω +υλοποιήσει με την σειρά που εκτελούνται στο πρόγραμμα. + +\section{Συναρτήσεις} + +\subsection{\lstinline{float input(const char *fmt, int i)}} + +Δίνει την δυνατότητα για formatted input. + +\subsection{\lstinline{float find(int flag)}} \label{find} + +Βρίσκει το $X_{min}$ και $X_{max}$ ανάλογα με το flag που +τής έχει δωθεί. Τα flags που μπορούνε να δωθούν είναι τα εξής +\begin{itemize} + \item \lstinline{FIND_XMIN} + \item \lstinline{FIND_XMAX} +\end{itemize} +Προκειμένου να υπολογίσει οποιαδήποτε από τις δύο τιμές ακολουθεί τον +εξής αλγόριθμο: +\begin{itemize} + \item Για κάθε στοιχείο του τοπικού διανύσματος του εκάστοτε + επεξεργαστή, βρες το τοπικό μέγιστο ή ελάχιστο. + \item Μάζεψε τα αποτέλεσματα από όλους τους επεξεργαστές στον + \lstinline{root}. + \item Βρες το ολικό μέγιστο ή ελάχιστο με την ίδια λογική όπως + στο πρώτο βήμα. + \item Στείλε το αποτέλεσμα σε όλους τους επεξεργαστές. +\end{itemize} + +\subsection{\lstinline{float calcavg(void)}} \label{calcavg} + +Υπολογίζει την ολική μέση τιμή. Αρχικά βρίσκει όλα τα +τοπικά μέγιστα, τα μαζεύει στον \lstinline{root} επεξεργαστή, +ο οποίος βρίσκει την ολική μέση τιμή και την στέλνει σε +όλους τους υπόλοιπους επεξεργαστές. + +\subsection{\lstinline{float findavg(float *v, int len)}} + +Βοηθητική συνάρτηση για υπολογισμό μέσης τιμής. Χρησιμοποιείται από την +\lstinline{calcavg} [\ref{calcavg}]. + +\subsection{\lstinline{int count(float avg, int flag)}} + +Υπολογίζει πόσα στοιχεία είναι είτε μεγαλύτερα είτε μικρότερα της ολικής +μέσης τιμής $m$. Το τί από τα δύο θα υπολογίσει εξαρτάται από το flag που +θα τής δωθεί. Τα flags που δέχεται είναι τα εξής: +\begin{itemize} + \item \lstinline{COUNT_BELOW_AVG} + \item \lstinline{COUNT_ABOVE_AVG} +\end{itemize} + +Για τους υπολογισμούς ακολουθεί παρόμοια λογική με την \lstinline{find} [\ref{find}]. + +\subsection{\lstinline{float calcvar(float avg)}} + +Υπολογίζει την ολική διασπορά των στοιχείων του διανύσματος $X$. Ως όρισμα +δέχεται την μέση τιμή $m$ που υπολογίζει η \lstinline{calcavg} [\ref{calcavg}]. +Ο τύπος που χρησιμοποιεί για τον υπολογισμό της τοπικής διασποράς είναι: +\[var_{local} = \sum_{i = 0}^{n_{local}} (x_{i} - m)^2\] +Αφού μαζέψει όλα τα τοπικά αποτελέσματα στον επεξεργαστή \lstinline{root}, +τα αθροίζει και τα διαιρεί δια $n - 1$, ώστε να ολοκληρωθεί ο τύπος της διασποράς. +\[var = \frac{1}{n - 1} \cdot \sum_{i = 0}^{n - 1} (x_i - m)^2\] + +\subsection{\lstinline{float *calcd(float xmin, float xmax)}} + +Δημιουργεί ένα νέο διάνυσμα $Δ$, του οποίο το κάθε στοιχείο $δ_i$ είναι ίσο +με +\[δ_i = ((x_i - x_{min}) / (x_{max} - x_{min})) \cdot 100 \] + +Αφού υπολογίσει τον παραπάνω τύπο για κάθε στοιχείο όλων των τοπικών διανυσμάτων, +μαζεύει όλα τα διανύσματα στον επεξεργαστή \lstinline{root} μέσω της +\lstinline{MPI_Gatherv(3)}. + +\subsection{\lstinline{Pair findmax(float *d)}} + +Βρίσκει το ολικό μέγιστο στο διάνυσμα $Δ$ καθώς και την θέση του στο διάνυσμα. +Αρχικά, αυτό που επιστρέφει η συνάρτηση είναι ένα \lstinline{struct Pair}, το +οποίο είναι ένα \lstinline{struct} που δημιούργησα ώστε να αποθηκεύσω τα δύο +αποτέλεσματα που θα παράξει η συνάρτηση αυτή ($D_{max}$ και $D_{maxloc}$). + +Ο αλγόριθμος που ακολουθεί η συνάρτηση είναι ο εξής: +\begin{itemize} + \item Για κάθε στοιχείο του γενικού διανύσματος $Δ$, ψάξε + το μέγιστο στοιχείο και την θέση του. + \item Αφού βρεθεί, με την χρήση της \lstinline{MPI_Reduce(3)}, + βρες την θέση το ολικό μέγιστο καθώς και την θέση του και αποθήκευσέ + τα στο \lstinline{out} στον επεξεργαστή \lstinline{root}. +\end{itemize} + +\subsection{\lstinline{float *calcpfxsums(void)}} + +Υπολογίζει το διάνυσμα προθεμάτων (prefix sums) των στοιχείων του +διανύσματος $X$. Προκειμένου να γίνει αυτό χρησιμοποείται η συνάρτηση +\lstinline{MPI_Scan(3)}, η οποία κάνει όλους τους απαραίτητους υπολογισμούς. +Εμείς το μόνο που έχουμε να κάνουμε είναι να αποθηκεύσουμε τα αποτελέσματα στον +πίνακα \lstinline{pfxsums}. Σημαντικό να σημειωθεί ότι αυτή η συνάρτηση +εκτελείται επιτυχώς \textit{μόνο} στην περίπτωση που $n = p$. + +\subsection{\lstinline{void printv(const char *str, const float *v)}} + +Βοηθτική συνάρτηση για να τυπώνει διανύσματα με πιο όμορφο τρόπο. + +\subsection{\lstinline{void *emalloc(size_t nb)}} + +\lstinline{malloc(3)} με error checks. + +\section{Κώδικας} +\lstinputlisting[language=C]{ex2.c} + +\section{Προβλήματα} + +Δεν κατάφερα να βρω πώς να υλοποιηθεί η εύρεση του διανύσματος προθεμάτων +στην περίπτωση που το $n \neq p$. + +\section{Ενδεικτικά τρεξίματα} + +Input: $p = 4$, $n = 7$, $X = \{1, 2, 3, 4, -1, -2, 6\}$ \\ + +\includegraphics[width=\textwidth]{res/run1.png} \\ + +Input: $p = 4$, $n = 11$, $X = \{1, 2, -5, 21, 13, 6, -10, 8, 9, 4, -6\}$ \\ + +\includegraphics[width=\textwidth]{res/run2.png} \\ + +Input: $p = 8$, $n = 8$, $X = \{2, 3, 4, 5, 6, -1, -2\}$ \\ +Εφόσον τώρα ισχύει $n = p$, η συνάρτηση για την έυρεση των +prefix sums θα λειτουργήσει σωστά. \\ + +\includegraphics[width=\textwidth]{res/run3.png} \\ + \end{document} diff --git a/c-parallel-computing/ex2/ex2.c b/c-parallel-computing/ex2/ex2.c @@ -2,18 +2,18 @@ #include <stdlib.h> #include <mpi.h> -/* constants */ +/* Flags */ #define FIND_XMIN 1 << 0 #define FIND_XMAX 1 << 1 #define COUNT_BELOW_AVG 1 << 2 #define COUNT_ABOVE_AVG 1 << 3 -struct Pair { - float val; /* max value in array */ - int i; /* index of max */ -}; +typedef struct { + float val; /* Max value in array */ + int i; /* Index of max */ +} Pair; -/* function declarations */ +/* Function declarations */ static float input(const char *, int); static float find(int); static float findavg(float *, int); @@ -21,18 +21,19 @@ static float calcavg(void); static int count(float, int); static float calcvar(float); static float *calcd(float, float); -struct Pair findmax(float *); +static Pair findmax(float *); static float *calcpfxsums(void); static void printv(const char *, const float *); static void *emalloc(size_t); -/* global variables */ +/* Global variables */ static int rank, nproc, root = 0; static int *scounts, *displs; static float *vec, *localvec; static int n, localn; -/* function implementations */ +/* Function implementations */ +/* Formatted input */ static float input(const char *fmt, int i) { @@ -43,9 +44,11 @@ input(const char *fmt, int i) printf("%s", buf); scanf("%f", &n); getchar(); + return n; } +/* Find `xmin` and `xmax` depending on the `flag` argument. */ static float find(int flag) { @@ -55,35 +58,47 @@ find(int flag) int i; res = emalloc(nproc * sizeof(float)); + /* + * Loop through each local vector and assign the local + * result depending on which of the two flags is set + */ for (i = 0; i < localn; i++) if ((flag & FIND_XMIN && localvec[i] < localres) || (flag & FIND_XMAX && localvec[i] > localres)) localres = localvec[i]; + /* Send local results to `root` */ MPI_Gather(&localres, 1, MPI_FLOAT, res, 1, MPI_FLOAT, root, MPI_COMM_WORLD); if (rank == root) + /* Same process as above */ for (i = 0; i < nproc; i++) if ((flag & FIND_XMIN && res[i] < finalres) || (flag & FIND_XMAX && res[i] > finalres)) finalres = res[i]; + /* Everyone has to know the final result */ MPI_Bcast(&finalres, 1, MPI_FLOAT, root, MPI_COMM_WORLD); free(res); return finalres; } +/* + * Small utility function for `calcavg` to avoid code duplication. + * Calcuates the average for a given vector + */ static float -findavg(float *vec, int len) +findavg(float *v, int len) { float sum = 0.0f; int i = 0; for (; i < len; i++) - sum += vec[i]; + sum += v[i]; return (sum / (float)len); } +/* Calculate the global average */ static float calcavg(void) { @@ -101,6 +116,10 @@ calcavg(void) return finalavg; } +/* + * Count how many elements are below or above average based on the + * `flag` argument. Similar logic as with `find` above. + */ static int count(float avg, int flag) { @@ -122,6 +141,7 @@ count(float avg, int flag) return finalres; } +/* Calculate the global variance */ static float calcvar(float avg) { @@ -145,6 +165,9 @@ calcvar(float avg) return finalvar; } +/* Generate D. A vector where each element is + * ((xi - xmin) / (xmax - xmin)) * 100. +*/ static float * calcd(float xmin, float xmax) { @@ -165,49 +188,49 @@ calcd(float xmin, float xmax) return finald; } -struct Pair +/* Find global max and MAXLOC */ +static Pair findmax(float *d) { - struct Pair in, out; - float *locald; - int i = 0; + Pair in, out; + int i = 1; - locald = emalloc(localn * sizeof(float)); - MPI_Scatterv(d, scounts, displs, MPI_FLOAT, locald, localn, MPI_FLOAT, - root, MPI_COMM_WORLD); - - in.val = *locald; + in.val = *d; in.i = 0; - for (; i < localn; i++) { - if (in.val < locald[i]) { - in.val = locald[i]; + for (; i < n; i++) { + if (in.val < d[i]) { + in.val = d[i]; in.i = i; } } in.i += rank * localn; MPI_Reduce(&in, &out, 1, MPI_FLOAT_INT, MPI_MAXLOC, root, MPI_COMM_WORLD); - free(locald); return out; } +/* Calucate the prefix sums of `vec`. Only world when + * n == nproc + */ static float * calcpfxsums(void) { - float *localsums, *finalsums; + float *pfxsums; float sum = 0.0f; - localsums = emalloc(nproc * sizeof(float)); - finalsums = emalloc(n * sizeof(float)); + pfxsums = emalloc(n * sizeof(float)); - MPI_Scan(localsums, &sum, 1, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD); - /*MPI_Gather(&sum, 1, MPI_FLOAT, finalsums, 1, MPI_FLOAT, root, MPI_COMM_WORLD);*/ + /* Scan each local vector and assign the result to `sum`. */ + MPI_Scan(localvec, &sum, 1, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD); + /* Be in sync. */ + MPI_Barrier(MPI_COMM_WORLD); + /* Get results in `pfxsums` */ + MPI_Gather(&sum, 1, MPI_FLOAT, pfxsums, 1, MPI_FLOAT, root, MPI_COMM_WORLD); - free(localsums); - - return finalsums; + return pfxsums; } +/* Utility function to print a vector in a prettier way. */ static void printv(const char *str, const float *v) { @@ -219,6 +242,7 @@ printv(const char *str, const float *v) printf("]\n"); } +/* Error checking `malloc(3)`. */ static void * emalloc(size_t nb) { @@ -234,16 +258,20 @@ emalloc(size_t nb) int main(int argc, char *argv[]) { - struct Pair dmax; + Pair dmax; float avg, var, xmin, xmax; float *d, *pfxsums; int belowavg, aboveavg; - int i; + int i, rc; - MPI_Init(&argc, &argv); + if ((rc = MPI_Init(&argc, &argv)) != 0) { + fprintf(stderr, "%s: cannot initialize MPI.\n", argv[0]); + MPI_Abort(MPI_COMM_WORLD, rc); + } MPI_Comm_size(MPI_COMM_WORLD, &nproc); MPI_Comm_rank(MPI_COMM_WORLD, &rank); + /* Read global vector. */ if (rank == root) { n = input("N: ", 0); vec = emalloc(n * sizeof(float)); @@ -251,51 +279,54 @@ main(int argc, char *argv[]) vec[i] = input("vec[%d]: ", i); } - /* move? */ + /* Send `n` to everyone. */ MPI_Bcast(&n, 1, MPI_INT, root, MPI_COMM_WORLD); + /* Will be used for `MPI_Scatterv(3)` and `MPI_Gatherv(3)` later on. */ scounts = emalloc(nproc * sizeof(int)); displs = emalloc(nproc * sizeof(int)); - for (i = 0; i < nproc; i++) { - /* TODO: explain */ + /* make it work even if `n` is not a multiple of `nproc`. */ scounts[i] = (i != nproc - 1) ? n / nproc : n / nproc + n % nproc; + /* take the last `scounts` so that we don't offset +1 each time. */ displs[i] = i * scounts[i != 0 ? i-1 : i]; } + /* + * Each local `n` is the same as the `scounts` of each process, so we + * assign it to `localn` for readablity. + */ localn = scounts[rank]; localvec = emalloc(localn * sizeof(float)); + /* Scatter the array to each process. */ MPI_Scatterv(vec, scounts, displs, MPI_FLOAT, localvec, localn, MPI_FLOAT, root, MPI_COMM_WORLD); - /* part 0.1 - calculate min and max */ + /* Part 0.1 - Calculate global minimum and maximum. */ xmin = find(FIND_XMIN); xmax = find(FIND_XMAX); - /* part 0.2 - calculate average */ + /* Part 0.2 - Calculate average. */ avg = calcavg(); - /* part 1 - find how many elements are above or below average */ + /* Part 1 - Find how many elements are above or below average. */ belowavg = count(avg, COUNT_BELOW_AVG); aboveavg = count(avg, COUNT_ABOVE_AVG); - /* part 2 - calculate variance */ + /* Part 2 - Calculate variance. */ var = calcvar(avg); - /* - * part 3 - make a new vector where each element is: - * ((xi - xmin) / (xmax - xmin)) * 100 - */ + /* Part 3 - Generate D. */ d = calcd(xmin, xmax); - /* part 4 - find dmax and dmaxloc */ + /* Part 4 - Find dmax and dmaxloc. */ dmax = findmax(d); - /* part 5 - prefixs sum of vec */ + /* Part 5 - Prefix sums of `vec`. */ pfxsums = calcpfxsums(); - /* print all results */ + /* Print all results */ if (rank == root) { printf("\n"); printv("X: ", vec); diff --git a/c-parallel-computing/ex2/res/run1.png b/c-parallel-computing/ex2/res/run1.png Binary files differ. diff --git a/c-parallel-computing/ex2/res/run2.png b/c-parallel-computing/ex2/res/run2.png Binary files differ. diff --git a/c-parallel-computing/ex2/res/run3.png b/c-parallel-computing/ex2/res/run3.png Binary files differ. diff --git a/sh-os1/ex2/bck b/sh-os1/ex2/bck @@ -1,24 +1,40 @@ #!/bin/sh +# ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED] + main() { - # allow > 3? + # We could allow more than 4 command line arguments + # since it's not going to make a difference, but for + # safety, we'll exit if they're not exactly 4. test $# -eq 3 || usage + # Give readable names to each argument. username=${1} src=${2} dst=${3} + # Error checks. + # In order for `username` to really be a user's name, it + # has to exist in `/etc/passwd`. test ! -z "$(grep -w "^${username}" /etc/passwd)" || err "'${username}' is not in /etc/passwd" + + # The -e options means anything (file, directory, device, etc.). test -e ${src} || err "${src}: no such file or directory" test -e ${dst} || err "${dst}: no such file or directory" + # Create an archive if `dst` is a directory. test -d ${dst} && tar -cvf "${dst}/${src##*/}.tar" ${src} - # handle tar.* + + # Append to `dst` only if it is a tar archive. If it's a non-archive + # then we cannot append since that would not make sense. + # `{dst##*.}` means that we want to get only its extension, if any. test ${dst##*.} = "tar" && tar -rf ${dst} ${src} } usage() { + # `{0##*/}` means the first argument (i.e the script's name) with + # its path stripped, if it exists. echo "usage: ${0##*/} username src dst" 1>&2 exit 1 } @@ -28,4 +44,5 @@ err() { exit 1 } +# Pass all command line arguments to `main`. main "$@" diff --git a/sh-os1/ex2/createpvs b/sh-os1/ex2/createpvs @@ -1,24 +1,37 @@ #!/bin/sh +# ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED] + main() { + # We could allow more than 4 command line arguments + # since it's not going to make a difference, but for + # safety, we'll exit if they're not exactly 4. test $# -eq 4 || usage + # Give readable names to each argument. rootdir=${1} ndbdirs=${2} ndatadirs=${3} username=${4} + # Error checks. test -d "${rootdir}" || err "${rootdir}: no such directory" isnumber ${ndbdirs} || err "'${ndbdirs}' is not a number" isnumber ${ndatadirs} || err "'${ndatadirs}' is not a number" + + # In order for `username` to really be a user's name, it + # has to exist in `/etc/passwd`. test ! -z "$(grep -w "^${username}" /etc/passwd)" || err "'${username}' is not in /etc/passwd" + # The actual exercise. makedirs "dbfolder" ${ndbdirs} makedirs "datafolder" ${ndatadirs} } usage() { + # `{0##*/}` means the first argument (i.e the script's name) with + # its path stripped, if it exists. echo "usage: ${0##*/} root_dir num_dbdirs num_datadirs username" 1>&2 exit 1 } @@ -29,20 +42,40 @@ err() { } isnumber() { + # If the argument does not begin *and* end in a number, then + # it's not a number. This also excludes negative numbers, but + # we shouldn't accept negative numbers anyway. test ! -z "$(echo "${1}" | grep "^[0-9]\+$")" } +# Make directories in the following format: dir1, dir2, dir3... +# If there are already directories with the same name (e.g dir1, dir2), we need +# to number the new ones taking the existing ones into account (i.e dir3, dir4....). makedirs() { + # Number of directories to create. n=${2} + + # Get the last directory, if any, so that we know what the last number was. lastdir=$(ls ${rootdir} | grep "${1}" | sed "s/${1}//g" | sort -V | tail -1) + + # If we didn't find any, we'll start numbering them from 1. test -z ${lastdir} && lastdir=0 + # If `n` was already 0 we won't do anything. while ! [ $(expr ${n}) = 0 ]; do + # `lastdir` now holds the next number. lastdir=$(expr ${lastdir} + 1) + + # Make a directory using the format described above + # and change the owner to `username`. mkdir "${rootdir}/${1}${lastdir}" && chown ${username}: "${rootdir}/${1}${lastdir}" + + # Decrement by 1 until we reach `n = 0` so that + # we can exit the loop. n=$(expr ${n} - 1) done } +# Pass all command line arguments to `main`. main "$@" diff --git a/sh-os1/ex2/mfproc b/sh-os1/ex2/mfproc @@ -1,5 +1,7 @@ #!/bin/sh +# ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED] + # Exit codes: # 0 Success # 1 No user @@ -7,47 +9,82 @@ # 3 Usage main() { - # TODO: explain getopts(1) + # Using getopts(1) because it's easier to parse + # optional command line options. getopts(1) works + # by passing it all the available options. If an option + # is followed by a ':', that means that getopts(1) is + # expecting an argument as well. If no argument is passed, + # it exits with an error code of 1. In this case I've + # passed it the string "u:s:" which means that the options + # are '-u' and '-s' and both of them take an argument. + # The argument is always stored in the `OPTARG` environmental + # variable. while getopts u:s: arg; do case "${arg}" in + # In order for `username` to really be a user's name, it + # has to exist in `/etc/passwd`. u) test ! -z "$(grep -w "^${OPTARG}" /etc/passwd)" || err "'${OPTARG}' is not in /etc/passwd" 1 + # Get UID from username. uid=$(id -u ${OPTARG}) ;; s) state="${OPTARG}" + # `state` needs to be either S, R or Z in order + # to be valid. validstate ${state} || usage ;; *) usage esac done count=0 + # Print header. expand(1) will will expand \t to 16 spaces + # in order for the output to be aligned. printf "Name\tPID\tPPID\tUID\tGID\tState\n" | expand -t 16 + + # Parse each process in `/proc`. This is a slow way to parse it though. for proc in /proc/*/status; do procuid=$(getfield "Uid:\s*${uid}" ${proc}) procgid=$(getfield "Gid" ${proc}) + # If the `-u` option was passed, ignore every UID that + # does not match the UID we gave it as an argument. + # We're using a `continue` command here so that it + # stop searching for anything else in this file since + # we don't need it. test -z ${uid} || [ "${procuid}" == "${uid}" ] || continue procstate=$(getfield "State:\s*${state}" ${proc}) + # Ignore any state other than S|R|Z validstate ${procstate} || continue + + # If the `-s` option was passed, ignore every state that does + # not match the one we gave it as an argument. test -z ${state} || [ "${procstate}" == "${state}" ] || continue procname=$(getfield "Name" ${proc}) procpid=$(getfield "Pid" ${proc}) procppid=$(getfield "PPid" ${proc}) + # Print everything in a top(1)-like format. printf "%s\t%s\t%s\t%s\t%s\t%s\n" \ ${procname} ${procpid} ${procppid} ${procuid} ${procgid} ${procstate} | expand -t 16 + + # Increment counter by 1 to keep track of how many processes + # we have printed. If `count` is 0 after the loop, the script + # will exit with error code 2, as is done below. count=$(expr ${count} + 1) done # We didn't print any process test ${count} -eq 0 && exit 2 + # Success! exit 0 } usage() { + # `{0##*/}` means the first argument (i.e the script's name) with + # its path stripped, if it exists. echo "usage: ${0##*/} [-u username] [-s S|R|Z]" 1>&2 exit 3 } @@ -57,12 +94,17 @@ err() { exit ${2} } +# Check if process is either S, R or Z. validstate() { [ "${1}" = "S" ] || [ "${1}" = "R" ] || [ "${1}" = "Z" ] } +# Get wanted field. grep(1) will return the line +# that begins with the first argument we passed. +# awk(1) is used to extract the 2nd field in the string. getfield() { grep -w "^${1}" ${2} | awk '{print $2}' } +# Pass all command line arguments to `main`. main "$@" diff --git a/sh-os1/ex2/searching b/sh-os1/ex2/searching @@ -1,32 +1,43 @@ #!/bin/sh +# ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED] + main() { + mode=${1} + days=${2} + # Command line arguments must be 2. test $# -eq 2 || usage - isnumber ${1} - test ${1} -ge 0 && test ${1} -le 7777 || err "'${1}' is not a valid mode" - isnumber ${2} + # Check if `1` and `2` are numbers, otherwise exit. + isnumber ${mode} || err "'${mode}' is not a valid number" + isnumber ${days} || err "'${days}' is not a valid number" + # In order for `mode` to really be a valid mode, `0 <= x <= 7777` must + # satisfied, since permission modes can range from 0 to 7777. + test ${mode} -ge 0 && test ${mode} -le 7777 || err "'${mode}' is not a valid mode" + # Pretty ugly loop. sum1=0; sum2=0; sum3=0; sum4=0; sum5=0 + # Exit if `cont` is "n". while [ ! "${cont}" = "n" ] ; do read -erp "Directory name: " dir + # Exit if nothing was typed. test ! -z "${dir}" || err "please give a directory name" + # Check if `dir` really is a directory. test -d "${dir}" || err "'${dir}' is not a directory" - # TODO: minimize calls - echo "Files with permissions ${1}: " - find "${dir}" -perm ${1} 2> /dev/null - sum1=$((${sum1}+$(find "${dir}" -perm ${1} 2> /dev/null | wc -l))) + echo "Files with permissions ${mode}: " + find "${dir}" -perm ${mode} 2> /dev/null + sum1=$((${sum1}+$(find "${dir}" -perm ${mode} 2> /dev/null | wc -l))) echo "" - echo "Files modified within the last ${2} days: " - find "${dir}" -mtime -${2} 2> /dev/null - sum2=$((${sum2}+$(find "${dir}" -mtime -${2} 2> /dev/null | wc -l))) + echo "Files modified within the last ${days} days: " + find "${dir}" -mtime -${days} 2> /dev/null + sum2=$((${sum2}+$(find "${dir}" -mtime -${days} 2> /dev/null | wc -l))) echo "" - echo "Subdirectories accessed within the last ${2} days: " - find "${dir}" -type d -atime -${2} 2> /dev/null - sum3=$((${sum3}+$(find "${dir}" -type d -atime -${2} 2> /dev/null | wc -l))) + echo "Subdirectories accessed within the last ${days} days: " + find "${dir}" -type d -atime -${days} 2> /dev/null + sum3=$((${sum3}+$(find "${dir}" -type d -atime -${days} 2> /dev/null | wc -l))) echo "" echo "Files which all users have read access to: " @@ -41,6 +52,7 @@ main() { read -erp "Do you want to continue (y/n)? " cont test "${cont}" = "n" && + printf "Total entries found for:\n1. %s\n2. %s\n3. %s\n4. %s\n5. %s\n" \ "${sum1}" "${sum2}" "${sum3}" "${sum4}" "${sum5}" done @@ -48,6 +60,8 @@ main() { } usage() { + # `{0##*/}` means the first argument (i.e the script's name) with + # its path stripped, if it exists. echo "usage: ${0##*/} perms days" 1>&2 exit 1 } @@ -58,7 +72,11 @@ err() { } isnumber() { - test ! -z "$(echo "${1}" | grep "^[0-9]\+$")" || err "'${1}' is not a valid number" + # If the argument does not begin *and* end in a number, then + # it's not a number. This also excludes negative numbers, but + # we shouldn't accept negative numbers anyway. + test ! -z "$(echo "${1}" | grep "^[0-9]\+$")" } +# Pass all command line arguments to `main`. main "$@" diff --git a/sh-os1/ex2/teldb b/sh-os1/ex2/teldb @@ -1,8 +1,12 @@ #!/bin/sh +# ΧΡΗΣΤΟΣ ΜΑΡΓΙΩΛΗΣ - [REDACTED] + +# We'll use this later. emptylnregex="^\ *$" main() { + # Parse command line options, exit if no option was passed. case ${1} in -a) newentry ;; -l) list ;; @@ -15,6 +19,10 @@ main() { } usage() { + # `{0##*/}` means the first argument (i.e the script's name) with + # its path stripped, if it exists. + # The {} around the options denote that only one option has to be + # passed to the script. echo "usage: ${0##*/} {-a | -l | -n | -c keyword | -d keyword {-b | -r} | -s col}" 1>&2 echo "" 1>&2 echo "options:" 1>&2 @@ -30,15 +38,21 @@ usage() { exit 1 } +# Exit with a `usage` message if the string that was passed is empty. +# This is used to make sure that we'll not write empty fields in `catalog`. errempty() { test ! -z "${1}" || usage } +# Skip empty lines skipempty() { grep -v "${emptylnregex}" } +# -a option - Make a new entry for `catalog`. newentry() { + # Read fields and include an error check so that + # we don't pass an empty field. read -erp "First name: " fname errempty "${fname}" @@ -51,38 +65,50 @@ newentry() { read -erp "Phone number: " phone errempty "${phone}" + # Append all the things we've read to `catalog`. printf "%s %s %s %s\n" "${fname}" "${lname}" "${town}" "${phone}" >> catalog } +# -l option - Print `catalog` with each line numbered. It also skips empty lines. list() { nl catalog | skipempty } +# -s option - Sort `catalog` by column. We pass the desired column in `main`. sortcol() { errempty "${1}" sort -k "${1}" catalog | skipempty } +# -c option - Print only the lines that match a specific keyword. filter() { errempty "${1}" grep "${1}" catalog } +# -d option - Delete a line. deleteln() { errempty "${1}" errempty "${2}" + # Parse sub-options. case ${2} in + # Replace line with an empty line. -b) sed -i "s/.*${1}.*//" catalog ;; + # Just delete the line. -r) sed -i "/${1}/d" catalog ;; esac } +# -n option - Count empty lines and delete them if the user answers with `y`. emptyln() { printf "Empty lines in 'catalog': " grep "${emptylnregex}" catalog | wc -l read -erp "Delete them (y/n)? " del + # Delete lines using `deleteln` with `-r` option so that it + # just deletes the line, without replacing it with an empty one. test "${del}" = "y" && deleteln "${emptylnregex}" "-r" } +# Pass all command line arguments to `main`. main "$@"