Cogs and Levers A blog full of technical stuff

Simple usages of rsync

Introduction

rsync is an excellent tool to have as your disposal for working with file data.

In today’s article, I’ll go through some usages.

Progress Copy

To securely and efficiently transfer data to (or from) a remote server while providing you progress updates you can use the following:

rsync -avzh --progress --stats user@server:/path/to/file output_name
  • -a puts rsync into archive mode, preserving your file and link structures
  • -v pumps up the logging output to give you verbose information
  • -z will compress the file data as it’s sent over the network
  • -h makes the output human readable
  • --progress provides a progress bar to be displayed, which tracks how far through we are
  • --stats provides a full statistical run down after the process has completed

Simple Backups with rsync

Introduction

Rsync is a powerful tool often utilized for its proficiency in file synchronization and transfer. Widely adopted in various computing environments, rsync excels in efficiently mirroring data across multiple platforms and minimizing data transfer by sending only changes in files. This utility is not only pivotal for maintaining backups but also ensures that the copies are consistent and up-to-date.

Today’s article is designed to guide you through the steps of setting up a basic yet effective backup system using rsync. By the end of this guide, you’ll have the knowledge to implement a reliable backup solution that can be customized to fit your specific needs.

Daily

The daily backup captures all of the changes for the day, giving you a backup with the fastest cadence.

#!/bin/bash

# daily-backup.sh
#
# Basic copy script to perform daily backups of the home dir

USER=$(whoami)
HOST=$(hostname)

rsync -aAX \
    --delete \
    --rsync-path="mkdir -p /path/to/backups/$HOST/daily/ && rsync" \
    --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    /home/$USER/ backup-user@backup-server:/path/to/backups/$HOST/daily/ 

Using whoami and hostname we can generalise this script so that you can reuse it between all of your machines.

The rsync-homedir-excludes.txt file allows you to define files that you’re not interested in backing up.

The switches that we’re sending into rsync are quite significant:

  • -a puts rsync into archive mode, preserving file structures, and links
  • -A preserves the ACLs so all of our permissions are preserved
  • -X any extra attributes that are stored by your file system will be preserved
  • --delete will delete files in the destination that are no longer present in the source, making the backup a true mirror

Weekly

The weekly backup is very similar to the daily backup. It’ll target different folders, and will have a different execution cadence.

#!/bin/bash

# weekly-backup.sh
#
# Basic copy script to perform weekly backups of the home dir

USER=$(whoami)
HOST=$(hostname)

rsync -aAX \
    --delete \
    --rsync-path="mkdir -p /path/to/backups/$HOST/weekly/ && rsync" \
    --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    /home/$USER/ backup-user@backup-server:/path/to/backups/$HOST/weekly/ 

There isn’t much difference here. Just writing to the /weekly folder.

Monthly

The longest cadence that we have is a monthly process which will archive the current state into an archive, and date the file for later use potentially.

#!/bin/bash

# monthly-backup.sh
#
# Monthly archive and copy

ARCHIVE=$(date +%Y-%m-%d).tar.gz
USER=$(whoami)
HOST=$(hostname)

tar --exclude-from=/home/$USER/.local/bin/rsync-homedir-excludes.txt \
    -zcvf \
    /tmp/$ARCHIVE \
    /home/$USER

scp /tmp/$ARCHIVE backup-user@backup-server:/path/to/backups/$HOST/monthly/

rm /tmp/$ARCHIVE

Using tar this script builds a full archive, and then sends that off to the backup server.

Scheduling

Finally, we need to setup these scripts to execute automatically for us. For this, we’ll use cron.

Here’s an example crontab scheduling these scripts:

# m h  dom mon dow   command
00 22 * * * /home/user/.local/bin/daily-backup.sh
00 21 * * 6 /home/user/.local/bin/weekly-backup.sh
00 22 1 * * /home/user/.local/bin/monthly-backup.sh

RAID array stability

Introduction

Recently, I’ve purchased a Terramaster D2-310 RAID array.

There were some important configurations that I needed to put together in order to get this unit to perform correctly.

Problems

After getting everything setup, I had two 4TB BarraCuda drives plugged in ready to go. I have this system running as a RAID 1 so that I have a mirror of my data.

After starting to transfer data onto the device, I noticed that copy jobs would grind to a halt; as well as the error log being full of the following:

[sda] tag#7 uas_zap_pending 0 uas-tag 13 inflight: CMD 
[sda] tag#7 CDB: Write(16) 8a 00 00 00 00 00 17 4f 50 10 00 00 04 00 00 00
[sda] tag#8 uas_zap_pending 0 uas-tag 14 inflight: CMD 
[sda] tag#8 CDB: Write(16) 8a 00 00 00 00 00 17 4f 50 00 00 00 00 10 00 00
[sda] tag#9 uas_zap_pending 0 uas-tag 15 inflight: CMD 
[sda] tag#9 CDB: Write(16) 8a 00 00 00 00 00 17 4f 4c 00 00 00 04 00 00 00
[sda] tag#10 uas_zap_pending 0 uas-tag 16 inflight: CMD 
[sda] tag#10 CDB: Write(16) 8a 00 00 00 00 00 17 4f 48 00 00 00 04 00 00 00
[sda] tag#11 uas_zap_pending 0 uas-tag 17 inflight: CMD 
[sda] tag#11 CDB: Write(16) 8a 00 00 00 00 00 17 4d 44 00 00 00 04 00 00 00
[sda] tag#13 uas_zap_pending 0 uas-tag 18 inflight: CMD 
[sda] tag#13 CDB: Synchronize Cache(10) 35 00 00 00 00 00 00 00 00 00

There’s also signs of crashes from the disk as well:

task:jbd2/sda1-8     state:D stack:0     pid:7414  tgi>
Call Trace:
 <TASK>
 __schedule+0x27c/0x6b0
 schedule+0x33/0x110
 io_schedule+0x46/0x80
 bit_wait_io+0x11/0x90
 __wait_on_bit+0x42/0x110
 ? __pfx_bit_wait_io+0x10/0x10
 out_of_line_wait_on_bit+0x8c/0xb0
 ? __pfx_wake_bit_function+0x10/0x10
 __wait_on_buffer+0x30/0x50
 jbd2_journal_commit_transaction+0x16da/0x1af0
 ? lock_timer_base+0x3b/0xe0
 kjournald2+0xab/0x280
 ? __pfx_autoremove_wake_function+0x10/0x10
 ? __pfx_kjournald2+0x10/0x10
 kthread+0xef/0x120
 ? __pfx_kthread+0x10/0x10
 ret_from_fork+0x44/0x70
 ? __pfx_kthread+0x10/0x10
 ret_from_fork_asm+0x1b/0x30
 </TASK>

Ugh …

Disable USB Auto Suspend

I thought there might have been a suspend issue with the USB device. In order to achieve this, I needed to send the kernel some parameters from GRUB.

Opening /etc/default/grub, we can start adding items to GRUB_CMDLINE_LINUX_DEFAULT:

GRUB_CMDLINE_LINUX_DEFAULT="usbcore.autosuspend=-1"

Failing back to usb_storage

Looking at some discussions I can see that a lot of people were having success with blacklisting the uas driver that the vendor provides, failing back to the standard driver from linux.

So, I did that too.

To do that, you need to pass the IDs as quirks into the blacklist request. To get those IDs, you need to look at the output from lsusb:

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 003 Device 002: ID 174c:2074 ASMedia Technology Inc. ASM1074 High-Speed hub
Bus 003 Device 003: ID 046d:c548 Logitech, Inc. Logi Bolt Receiver
Bus 003 Device 004: ID 8087:0033 Intel Corp. AX211 Bluetooth
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 004 Device 002: ID 174c:3074 ASMedia Technology Inc. ASM1074 SuperSpeed hub
Bus 004 Device 003: ID 0480:a006 Toshiba America Inc UAS Controller

The Toshiba, and ASMedia items were of interest to me.

Create a file /etc/modprobe.d/blacklist-uas.conf

I added this text:

options usb-storage quirks=174c:2074:u,174c:3074:u,0480:a006:u

Remember to specify the u at the end of the ID strings. It’s literal, and need to be there.

Conclusion

This thing works - and works really well now.

Linux PAM

Introduction

PAM (Pluggable Authentication Modules) is a flexible mechanism for authenticating users, which has become a fundamental part of system security on Linux and other Unix-like operating systems. PAM abstracts a range of authentication tasks into modular plugins that can be tailored to suit the needs of system administrators, providing a way to develop programs that are independent of authentication scheme specifics. This modularity not only enhances security but also simplifies the management of user authentication.

In today’s guide, we will delve into the process of creating a program that interacts with the Linux passwd system using PAM. By the end of this article, you’ll have a clear understanding of how to harness PAM’s capabilities to authenticate users in your own applications, ensuring secure and efficient access control.

Architecture

PAM (Pluggable Authentication Modules) employ a unique architecture that separates the specific implementation of authentication methods from the application programming interface (API).

At the heart of this design is the concept of “conversations,” a mechanism that facilitates communication between the application and the authentication modules.

This conversation-based model allows PAM to present a uniform interface to the application, regardless of the underlying authentication process.

As a result, developers can integrate various authentication technologies into their applications without having to tailor their code to each method.

Instead, they rely on PAM to handle the specifics through configurable modules, each responsible for a different aspect of the authentication process.

This abstraction not only simplifies development but also enhances the flexibility and scalability of security systems, accommodating a wide range of authentication schemes with minimal changes to core application code.

Conversation

The conversation function is the callback that PAM uses for interactivity. In this code snippet, we’re using the password of "password" to stuff into the response to use. You could use the conversation opportunity to ask for input from the user.

int converse(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
    *resp = NULL;
    
    if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
        return PAM_CONV_ERR;
    }
    
    struct pam_response *response =
        malloc(num_msg * sizeof(struct pam_response));

    if (response == NULL) {
        return PAM_BUF_ERR;
    }
    
    for (int i = 0; i < num_msg; ++i) {
        if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
            // supply the password directly here
            const char *password = "password";
            response[i].resp = strdup(password);
        } else {
            // Handle other message styles if needed
            response[i].resp = NULL;
        }
        response[i].resp_retcode = 0;
    }
    
    *resp = response;
    return PAM_SUCCESS;
}

You can see that there’s a preference asked for here with the message style of PAM_PROMPT_ECHO_OFF. This would be an indicator back to the user interface to not echo the user’s keystrokes to the interface as they type.

Test Harness

We can now use this conversation function with the pam library:

int main() {
    const char *username = "username";
    const char *service = "login";

    pam_handle_t *pamh = NULL;
    struct pam_conv conv = { converse, NULL };

    int retval = pam_start(service, username, &conv, &pamh);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "pam_start failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    retval = pam_authenticate(pamh, 0);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "Authentication failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    printf("Authentication successful!\n");

    pam_end(pamh, retval);
    return 0;
}

The pam_start function begins the authentication conversation. The first parameter supplied is the service, and in this case it’s set to "login". PAM uses this value and looks for a configuration file named /etc/pam.d/login. This is pretty standard on any system. This particular service is for the “Shadow ‘login’ service.

The username variable should contain the name of a registered user.

Our conversation function converse is supplied to pam_start via the conv variable.

We then use pam_authenticate to preform the conversation, and pam_end will do any clean up for us.

A full example

#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>

int converse(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
    *resp = NULL;
    
    if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
        return PAM_CONV_ERR;
    }
    
    struct pam_response *response =
        malloc(num_msg * sizeof(struct pam_response));

    if (response == NULL) {
        return PAM_BUF_ERR;
    }
    
    for (int i = 0; i < num_msg; ++i) {
        if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF) {
            // supply the password directly here
            const char *password = "password";
            response[i].resp = strdup(password);
        } else {
            // Handle other message styles if needed
            response[i].resp = NULL;
        }
        response[i].resp_retcode = 0;
    }
    
    *resp = response;
    return PAM_SUCCESS;
}

int main() {
    const char *username = "username";
    const char *service = "login";

    pam_handle_t *pamh = NULL;
    struct pam_conv conv = { converse, NULL };

    int retval = pam_start(service, username, &conv, &pamh);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "pam_start failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    retval = pam_authenticate(pamh, 0);

    if (retval != PAM_SUCCESS) {
        fprintf(stderr, "Authentication failed: %s\n", pam_strerror(pamh, retval));
        return 1;
    }

    printf("Authentication successful!\n");

    pam_end(pamh, retval);
    return 0;
}

Building

In order to build this test program you need to link with pam and pam_misc.

gcc auth.c -o auth -lpam -lpam_misc

Arbitrary length arithmetic with GMP

Introduction

GMP is a library that will allow you to perform calculations on numbers that extend past the reach of what your standard data sizes can hold. From their website:

GMP is a free library for arbitrary precision arithmetic, operating on signed integers, rational numbers, and floating-point numbers. There is no practical limit to the precision except the ones implied by the available memory in the machine GMP runs on. GMP has a rich set of functions, and the functions have a regular interface.

This means you can embed large math into your programs and will only be limited by the amount of memory on your running system.

In today’s article, we’ll go through a few simple examples on how to use this library.

Getting setup

Get gmp installed locally on your system either by downloading the latest release from their site, or just using your package manager.

sudo apt-get install libgmp10

Building

For any program that will require the gmp library, we’ll need to add the -lgmp switch:

gcc test.c -o test -lgmp

Now, we’re ready to go.

Factorial

As an example implementation, we’ll write a program to calculate the n’th factorial for us. First of all, we’ll implement this using traditional data types offered to us through C, and then we’ll swap this out to get greater numbers.

/* test1.c */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/** Compute the n'th factorial */
int factorial(int n) {
  int i;
  int x = 1;

  for (i = 1; i <= n; ++i) {
    x = x * i;
  }

  return x;
}

int main(int argc, char *argv[]) {
  int x;

  /* check that a number was specified */
  if (argc <= 1) {
    printf("usage: %s <number>\n", argv[0]);
    return 1;
  }

  /* convert the input to a number */
  x = atoi(argv[1]);
  assert(x >= 0);

  printf("%d\n", factorial(x));

  return 0;
}

We build this application not needing the gmp library:

gcc test1.c -o test1

We can then start to test it out.

$ ./test1 2  
2
$ ./test1 4
24
$ ./test1 5
120
$ ./test1 8
40320
$ ./test1 15
2004310016

The wheels start to fall off once we want to look at numbers higher than !19.

$ ./test1 19
109641728
$ ./test1 20
-2102132736

We overflowed our integer to where it wrapped into negative numbers. 19 is our limit for traditional data types.

Make the numbers bigger!

Now we can introduce gmp to help us break out of these constraints.

We’ll rewrite the factorial function above to operate on gmp types, and we’ll also convert the body of our program into a input capture function that will parse text into a gmp type for us.

Let’s deal with the input first:

void atoi_mpz(char *s, mpz_t res) {
  assert(s);
  
  int parse_result;

  /* allocate our number, and set an initial value of 0 */
  mpz_set_ui(res, 0);

  /* we assume base-10 when parsing */
  parse_result = mpz_set_str(res, s, 10);
  /* check that parsing was successful */
  assert(parse_result == 0);
}

First, you’ll notice that we’re not returning anything here. The mpz_t type is typed as an array, and as such can’t be used as a return. So, we supply it as an output parameter. This pattern will reoccur through these examples.

This function also assumes that res has already had mpz_init run on it, so it’s not magically allocating resources on your behalf.

mpz_set_ui sets the initial state of an mpz_t with a value from an integer (the real world!). mpz_set_str is really doing most of the work for us here, parsing out a string that its given into a mpz_t. The base needs to be supplied.

Now the factorial function will need to change as you’d expect:

/** Compute the n'th factorial */
void factorial(mpz_t n, mpz_t res) {
  mpz_t i;

  /* initialize x and set it to n */
  mpz_set(res, n);

  /* initialize i to 1 */
  mpz_init(i);
  mpz_set_ui(i, 1);

  while (mpz_cmp(i, n) < 0) {
    mpz_add_ui(i, i, 1);
    mpz_mul(res, res, i);
  }
 
  /* clean up work vars */
  mpz_clear(i);
}

Again, res is an output parameter with no return value. We do have a “work” variable here, so we set it up and destroy it all within the context of our function so we don’t have a memory leak.

mpz_cmp takes care of the looping for us. We’ve substituted a for loop here for a while loop to accommodate.

Full program!

Now we can use these functions in a program of our own. Here’s a full listing of an application that will give you the factorial of an arbitrary length integer.

/* test2.c */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <gmp.h>

/** Compute the n'th factorial */
void factorial(mpz_t n, mpz_t res) {
  mpz_t i;

  /* inintialize x and set it to n */
  mpz_set(res, n);

  /* initialize i to 1 */
  mpz_init(i);
  mpz_set_ui(i, 1);

  while (mpz_cmp(i, n) < 0) {
    mpz_add_ui(i, i, 1);
    mpz_mul(res, res, i);
  }
 
  /* clean up work vars */
  mpz_clear(i);
}

void atoi_mpz(char *s, mpz_t res) {
  assert(s);
  
  int parse_result;

  /* allocate our number, and set an initial value of 0 */
  mpz_set_ui(res, 0);

  /* we assume base-10 when parsing */
  parse_result = mpz_set_str(res, s, 10);
  /* check that parsing was successful */
  assert(parse_result == 0);
}

int main(int argc, char *argv[]) {
  mpz_t x, f;

  /* check that a number was specified */
  if (argc <= 1) {
    printf("usage: %s <number>\n", argv[0]);
    return 1;
  }

  mpz_init(f);
  mpz_init(x);

  atoi_mpz(argv[1], x);
  factorial(x, f);

  mpz_out_str(stdout, 10, f);

  mpz_clear(x);
  mpz_clear(f);

  return 0;
}

We write the result number here with mpz_out_str. This can redirected to any stream of your choice.

Building this program is just adding the lgmp switch.

gcc test2.c -o test2 -lgmp

And, now you can calculate factorials as high as you like:

$ ./test2 5
600
$ ./test2 50
1520704660085668902180630408303238442218882078448025600000000000000
$ ./test2 508
2600912719299986667129836551161461729376055224067622828051124610945736767140539144806269014448212661926636052457507242531933990603974750482248704223132435196370906188185711501338510076590779372857463616608930177975159159937930181522646905544337180113404654114353667383757400970162882750213805498362693213099688856210937449924868526775622360531973548790490474776119049615868165281085383657990705679209654697390907170752684309098744014502095309729615191639442748317478208592870187172780119819882401997425092893189361279318323502318101157291409635548371757588486399668194699833678157051688803686737440480747043533781525086057498368946374944369614791535224963761053372201191517194843602022799832918767988197763611904441080442538109356964428607751413413409857782381537871839805616626750150075526770316820978596423627176357508773710490144878649275528649481402223674961313005296813402084900146024228278827847133177811762165315793180635312968824483728409835808810724283538893336754191301602850516144797238826325624248081953676797029938259558400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000